Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the sql ssl (client) cert resource, sibling of TPG #2290. #134

Merged
merged 1 commit into from
Nov 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions google-beta/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ func Provider() terraform.ResourceProvider {
"google_spanner_database_iam_policy": ResourceIamPolicyWithImport(IamSpannerDatabaseSchema, NewSpannerDatabaseIamUpdater, SpannerDatabaseIdParseFunc),
"google_sql_database": resourceSqlDatabase(),
"google_sql_database_instance": resourceSqlDatabaseInstance(),
"google_sql_ssl_cert": resourceSqlSslCert(),
"google_sql_user": resourceSqlUser(),
"google_organization_iam_binding": ResourceIamBindingWithImport(IamOrganizationSchema, NewOrganizationIamUpdater, OrgIdParseFunc),
"google_organization_iam_custom_role": resourceGoogleOrganizationIamCustomRole(),
Expand Down
177 changes: 177 additions & 0 deletions google-beta/resource_sql_ssl_cert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package google

import (
"fmt"
"log"

"github.com/hashicorp/terraform/helper/schema"
"google.golang.org/api/sqladmin/v1beta4"
)

func resourceSqlSslCert() *schema.Resource {
return &schema.Resource{
Create: resourceSqlSslCertCreate,
Read: resourceSqlSslCertRead,
Delete: resourceSqlSslCertDelete,

SchemaVersion: 1,

Schema: map[string]*schema.Schema{
"common_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"instance": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"cert": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},

"cert_serial_number": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},

"create_time": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},

"expiration_time": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},

"private_key": &schema.Schema{
Type: schema.TypeString,
Computed: true,
Sensitive: true,
},

"server_ca_cert": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},

"sha1_fingerprint": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}

func resourceSqlSslCertCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)

project, err := getProject(d, config)
if err != nil {
return err
}

instance := d.Get("instance").(string)
commonName := d.Get("common_name").(string)

sslCertsInsertRequest := &sqladmin.SslCertsInsertRequest{
CommonName: commonName,
}

mutexKV.Lock(instanceMutexKey(project, instance))
defer mutexKV.Unlock(instanceMutexKey(project, instance))
resp, err := config.clientSqlAdmin.SslCerts.Insert(project, instance, sslCertsInsertRequest).Do()
if err != nil {
return fmt.Errorf("Error, failed to insert "+
"ssl cert %s into instance %s: %s", commonName, instance, err)
}

err = sqladminOperationWait(config, resp.Operation, project, "Create Ssl Cert")
if err != nil {
return fmt.Errorf("Error, failure waiting for creation of %q "+
"in %q: %s", commonName, instance, err)
}

fingerprint := resp.ClientCert.CertInfo.Sha1Fingerprint
d.SetId(fmt.Sprintf("%s/%s", instance, fingerprint))
d.Set("sha1_fingerprint", fingerprint)

// The private key is only returned on the initial insert so set it here.
d.Set("private_key", resp.ClientCert.CertPrivateKey)
d.Set("server_ca_cert", resp.ServerCaCert.Cert)

return resourceSqlSslCertRead(d, meta)
}

func resourceSqlSslCertRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)

project, err := getProject(d, config)
if err != nil {
return err
}

instance := d.Get("instance").(string)
commonName := d.Get("common_name").(string)
fingerprint := d.Get("sha1_fingerprint").(string)

sslCerts, err := config.clientSqlAdmin.SslCerts.Get(project, instance, fingerprint).Do()
if err != nil {
return handleNotFoundError(err, d, fmt.Sprintf("SQL Ssl Cert %q in instance %q", commonName, instance))
}

if sslCerts == nil {
log.Printf("[WARN] Removing SQL Ssl Cert %q because it's gone", commonName)
d.SetId("")

return nil
}

d.Set("instance", sslCerts.Instance)
d.Set("sha1_fingerprint", sslCerts.Sha1Fingerprint)
d.Set("common_name", sslCerts.CommonName)
d.Set("cert", sslCerts.Cert)
d.Set("cert_serial_number", sslCerts.CertSerialNumber)
d.Set("create_time", sslCerts.CreateTime)
d.Set("expiration_time", sslCerts.ExpirationTime)

d.SetId(fmt.Sprintf("%s/%s", instance, fingerprint))
return nil
}

func resourceSqlSslCertDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)

project, err := getProject(d, config)
if err != nil {
return err
}

instance := d.Get("instance").(string)
commonName := d.Get("common_name").(string)
fingerprint := d.Get("sha1_fingerprint").(string)

mutexKV.Lock(instanceMutexKey(project, instance))
defer mutexKV.Unlock(instanceMutexKey(project, instance))
op, err := config.clientSqlAdmin.SslCerts.Delete(project, instance, fingerprint).Do()

if err != nil {
return fmt.Errorf("Error, failed to delete "+
"ssl cert %q in instance %q: %s", commonName,
instance, err)
}

err = sqladminOperationWait(config, op, project, "Delete Ssl Cert")

if err != nil {
return fmt.Errorf("Error, failure waiting for deletion of ssl cert %q "+
"in %q: %s", commonName, instance, err)
}

return nil
}
136 changes: 136 additions & 0 deletions google-beta/resource_sql_ssl_cert_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package google

import (
"fmt"
"testing"

"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)

func TestAccSqlClientCert_mysql(t *testing.T) {
t.Parallel()

instance := acctest.RandomWithPrefix("i")
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccSqlClientCertDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testGoogleSqlClientCert_mysql(instance),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleSqlClientCertExists("google_sql_ssl_cert.cert1"),
testAccCheckGoogleSqlClientCertExists("google_sql_ssl_cert.cert2"),
),
},
},
})
}

func TestAccSqlClientCert_postgres(t *testing.T) {
t.Parallel()

instance := acctest.RandomWithPrefix("i")
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccSqlClientCertDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testGoogleSqlClientCert_postgres(instance),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleSqlClientCertExists("google_sql_ssl_cert.cert"),
),
},
},
})
}

func testAccCheckGoogleSqlClientCertExists(n string) resource.TestCheckFunc {
return func(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Resource not found: %s", n)
}

instance := rs.Primary.Attributes["instance"]
fingerprint := rs.Primary.Attributes["sha1_fingerprint"]
sslClientCert, err := config.clientSqlAdmin.SslCerts.Get(config.Project, instance, fingerprint).Do()

if err != nil {
return err
}

if sslClientCert.Instance == instance && sslClientCert.Sha1Fingerprint == fingerprint {
return nil
}

return fmt.Errorf("Not found: %s: %s", n, err)
}
}

func testAccSqlClientCertDestroy(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
config := testAccProvider.Meta().(*Config)
if rs.Type != "google_sql_ssl_cert" {
continue
}

fingerprint := rs.Primary.Attributes["sha1_fingerprint"]
instance := rs.Primary.Attributes["instance"]
sslCert, _ := config.clientSqlAdmin.SslCerts.Get(config.Project, instance, fingerprint).Do()

commonName := rs.Primary.Attributes["common_name"]
if sslCert != nil {
return fmt.Errorf("Client cert %q still exists, should have been destroyed", commonName)
}

return nil
}

return nil
}

func testGoogleSqlClientCert_mysql(instance string) string {
return fmt.Sprintf(`
resource "google_sql_database_instance" "instance" {
name = "%s"
region = "us-central"
settings {
tier = "D0"
}
}

resource "google_sql_ssl_cert" "cert1" {
common_name = "cert1"
instance = "${google_sql_database_instance.instance.name}"
}

resource "google_sql_ssl_cert" "cert2" {
common_name = "cert2"
instance = "${google_sql_database_instance.instance.name}"
}
`, instance)
}

func testGoogleSqlClientCert_postgres(instance string) string {
return fmt.Sprintf(`
resource "google_sql_database_instance" "instance" {
name = "%s"
region = "us-central1"
database_version = "POSTGRES_9_6"

settings {
tier = "db-f1-micro"
}
}

resource "google_sql_ssl_cert" "cert" {
common_name = "cert"
instance = "${google_sql_database_instance.instance.name}"
}
`, instance)
}
63 changes: 63 additions & 0 deletions website/docs/r/sql_ssl_cert.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
layout: "google"
page_title: "Google: google_sql_ssl_cert"
sidebar_current: "docs-google-sql-ssl-cert"
description: |-
Creates a new SQL Ssl Cert in Google Cloud SQL.
---

# google\_client\_cert

Creates a new Google SQL SSL Cert on a Google SQL Instance. For more information, see the [official documentation](https://cloud.google.com/sql/), or the [JSON API](https://cloud.google.com/sql/docs/mysql/admin-api/v1beta4/sslCerts).

~> **Note:** All arguments including the private key will be stored in the raw state as plain-text.
[Read more about sensitive data in state](/docs/state/sensitive-data.html).

## Example Usage

Example creating a SQL Client Certificate.

```hcl
resource "google_sql_database_instance" "master" {
name = "master-instance"

settings {
tier = "D0"
}
}

resource "google_sql_ssl_cert" "client_cert" {
common_name = "client-name"
instance = "${google_sql_database_instance.master.name}"
}
```

## Argument Reference

The following arguments are supported:

* `instance` - (Required) The name of the Cloud SQL instance. Changing this
forces a new resource to be created.

* `common_name` - (Required) The common name to be used in the certificate to identify the
client. Constrained to [a-zA-Z.-_ ]+. Changing this forces a new resource to be created.


## Attributes Reference

In addition to the arguments listed above, the following computed attributes are
exported:

* `sha1_fingerprint` - The SHA1 Fingerprint of the certificate.
* `private_key` - The private key associated with the client certificate.
* `server_ca_cert` - The CA cert of the server this client cert was generated from.
* `cert` - The actual certificate data for this client certificate.
* `cert_serial_number` - The serial number extracted from the certificate data.
* `create_time` - The time when the certificate was created in RFC 3339 format,
for example 2012-11-15T16:19:00.094Z.
* `expiration_time` - The time when the certificate expires in RFC 3339 format,
for example 2012-11-15T16:19:00.094Z.

## Import

Since the contents of the certificate cannot be accessed after its creation, this resource cannot be imported.
4 changes: 4 additions & 0 deletions website/google.erb
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,10 @@
<a href="/docs/providers/google/r/sql_database_instance.html">google_sql_database_instance</a>
</li>

<li<%= sidebar_current("docs-google-sql-ssl-cert") %>>
<a href="/docs/providers/google/r/sql_ssl_cert.html">google_sql_ssl_cert</a>
</li>

<li<%= sidebar_current("docs-google-sql-user") %>>
<a href="/docs/providers/google/r/sql_user.html">google_sql_user</a>
</li>
Expand Down