From a6a5a8093713a6d5df4e508fd701f4866ab2e3ab Mon Sep 17 00:00:00 2001 From: Nathan McKinley Date: Wed, 14 Nov 2018 10:59:20 -0800 Subject: [PATCH] Merge pull request #134 from modular-magician/codegen-pr-912 Add the sql ssl (client) cert resource, sibling of TPG #2290. --- google-beta/provider.go | 1 + google-beta/resource_sql_ssl_cert.go | 177 ++++++++++++++++++++++ google-beta/resource_sql_ssl_cert_test.go | 136 +++++++++++++++++ website/docs/r/sql_ssl_cert.html.markdown | 63 ++++++++ website/google.erb | 4 + 5 files changed, 381 insertions(+) create mode 100644 google-beta/resource_sql_ssl_cert.go create mode 100644 google-beta/resource_sql_ssl_cert_test.go create mode 100644 website/docs/r/sql_ssl_cert.html.markdown diff --git a/google-beta/provider.go b/google-beta/provider.go index 69c148aca7..e94f12093d 100644 --- a/google-beta/provider.go +++ b/google-beta/provider.go @@ -198,6 +198,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(), diff --git a/google-beta/resource_sql_ssl_cert.go b/google-beta/resource_sql_ssl_cert.go new file mode 100644 index 0000000000..7337b3fbdd --- /dev/null +++ b/google-beta/resource_sql_ssl_cert.go @@ -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 +} diff --git a/google-beta/resource_sql_ssl_cert_test.go b/google-beta/resource_sql_ssl_cert_test.go new file mode 100644 index 0000000000..90df41e0eb --- /dev/null +++ b/google-beta/resource_sql_ssl_cert_test.go @@ -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) +} diff --git a/website/docs/r/sql_ssl_cert.html.markdown b/website/docs/r/sql_ssl_cert.html.markdown new file mode 100644 index 0000000000..b2563c395e --- /dev/null +++ b/website/docs/r/sql_ssl_cert.html.markdown @@ -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. diff --git a/website/google.erb b/website/google.erb index 21ca810a88..c226444d97 100644 --- a/website/google.erb +++ b/website/google.erb @@ -667,6 +667,10 @@ google_sql_database_instance + > + google_sql_ssl_cert + + > google_sql_user