From 56f89e20d744d7ce183e77a312af0a7ab6057101 Mon Sep 17 00:00:00 2001 From: Riley Karson Date: Mon, 22 May 2017 13:43:11 -0700 Subject: [PATCH] provider/google: Add import support to google_sql_user (#14457) * Support importing google_sql_user * Updated documentation to reflect that passwords are not retrieved. * Added additional documentation detailing use. * Removed unneeded d.setId() line from GoogleSqlUser Read method. * Changed an errors.New() call to fmt.Errorf(). * Migrate schemas of existing GoogleSqlUser resources. * Remove explicitly setting 'id' property * Added google_sql_user to importability page. * Changed separator to '/' from '.' and updated tests + debug messages. --- .../providers/google/import_sql_user_test.go | 32 ++++++++ builtin/providers/google/resource_sql_user.go | 42 +++++++--- .../google/resource_sql_user_migrate.go | 39 +++++++++ .../google/resource_sql_user_migrate_test.go | 81 +++++++++++++++++++ .../source/docs/import/importability.html.md | 1 + .../providers/google/r/sql_user.html.markdown | 17 +++- 6 files changed, 199 insertions(+), 13 deletions(-) create mode 100644 builtin/providers/google/import_sql_user_test.go create mode 100644 builtin/providers/google/resource_sql_user_migrate.go create mode 100644 builtin/providers/google/resource_sql_user_migrate_test.go diff --git a/builtin/providers/google/import_sql_user_test.go b/builtin/providers/google/import_sql_user_test.go new file mode 100644 index 000000000..ea58f1aa0 --- /dev/null +++ b/builtin/providers/google/import_sql_user_test.go @@ -0,0 +1,32 @@ +package google + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccGoogleSqlUser_importBasic(t *testing.T) { + resourceName := "google_sql_user.user" + user := acctest.RandString(10) + instance := acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccGoogleSqlUserDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleSqlUser_basic(instance, user), + }, + + resource.TestStep{ + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"password"}, + }, + }, + }) +} diff --git a/builtin/providers/google/resource_sql_user.go b/builtin/providers/google/resource_sql_user.go index 23daf461a..afcc88e19 100644 --- a/builtin/providers/google/resource_sql_user.go +++ b/builtin/providers/google/resource_sql_user.go @@ -3,9 +3,9 @@ package google import ( "fmt" "log" + "strings" "github.com/hashicorp/terraform/helper/schema" - "google.golang.org/api/sqladmin/v1beta4" ) @@ -15,6 +15,12 @@ func resourceSqlUser() *schema.Resource { Read: resourceSqlUserRead, Update: resourceSqlUserUpdate, Delete: resourceSqlUserDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + SchemaVersion: 1, + MigrateState: resourceSqlUserMigrateState, Schema: map[string]*schema.Schema{ "host": &schema.Schema{ @@ -36,8 +42,9 @@ func resourceSqlUser() *schema.Resource { }, "password": &schema.Schema{ - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, + Sensitive: true, }, "project": &schema.Schema{ @@ -77,6 +84,8 @@ func resourceSqlUserCreate(d *schema.ResourceData, meta interface{}) error { "user %s into instance %s: %s", name, instance, err) } + d.SetId(fmt.Sprintf("%s/%s", instance, name)) + err = sqladminOperationWait(config, op, "Insert User") if err != nil { @@ -95,8 +104,16 @@ func resourceSqlUserRead(d *schema.ResourceData, meta interface{}) error { return err } - name := d.Get("name").(string) - instance := d.Get("instance").(string) + instanceAndName := strings.SplitN(d.Id(), "/", 2) + if len(instanceAndName) != 2 { + return fmt.Errorf( + "Wrong number of arguments when specifying imported id. Expected: 2. Saw: %d. Expected Input: $INSTANCENAME/$SQLUSERNAME Input: %s", + len(instanceAndName), + d.Id()) + } + + instance := instanceAndName[0] + name := instanceAndName[1] users, err := config.clientSqlAdmin.Users.List(project, instance).Do() @@ -104,23 +121,24 @@ func resourceSqlUserRead(d *schema.ResourceData, meta interface{}) error { return handleNotFoundError(err, d, fmt.Sprintf("SQL User %q in instance %q", name, instance)) } - found := false - for _, user := range users.Items { - if user.Name == name { - found = true + var user *sqladmin.User + for _, currentUser := range users.Items { + if currentUser.Name == name { + user = currentUser break } } - if !found { + if user == nil { log.Printf("[WARN] Removing SQL User %q because it's gone", d.Get("name").(string)) d.SetId("") return nil } - d.SetId(name) - + d.Set("host", user.Host) + d.Set("instance", user.Instance) + d.Set("name", user.Name) return nil } diff --git a/builtin/providers/google/resource_sql_user_migrate.go b/builtin/providers/google/resource_sql_user_migrate.go new file mode 100644 index 000000000..7f52771ad --- /dev/null +++ b/builtin/providers/google/resource_sql_user_migrate.go @@ -0,0 +1,39 @@ +package google + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform/terraform" +) + +func resourceSqlUserMigrateState( + v int, is *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) { + if is.Empty() { + log.Println("[DEBUG] Empty InstanceState; nothing to migrate.") + return is, nil + } + + switch v { + case 0: + log.Println("[INFO] Found Google Sql User State v0; migrating to v1") + is, err := migrateSqlUserStateV0toV1(is) + if err != nil { + return is, err + } + return is, nil + default: + return is, fmt.Errorf("Unexpected schema version: %d", v) + } +} + +func migrateSqlUserStateV0toV1(is *terraform.InstanceState) (*terraform.InstanceState, error) { + log.Printf("[DEBUG] Attributes before migration: %#v", is.Attributes) + + name := is.Attributes["name"] + instance := is.Attributes["instance"] + is.ID = fmt.Sprintf("%s/%s", instance, name) + + log.Printf("[DEBUG] Attributes after migration: %#v", is.Attributes) + return is, nil +} diff --git a/builtin/providers/google/resource_sql_user_migrate_test.go b/builtin/providers/google/resource_sql_user_migrate_test.go new file mode 100644 index 000000000..5e03d8d75 --- /dev/null +++ b/builtin/providers/google/resource_sql_user_migrate_test.go @@ -0,0 +1,81 @@ +package google + +import ( + "testing" + + "github.com/hashicorp/terraform/terraform" +) + +func TestSqlUserMigrateState(t *testing.T) { + cases := map[string]struct { + StateVersion int + Attributes map[string]string + Expected map[string]string + Meta interface{} + ID string + ExpectedID string + }{ + "change id from $NAME to $INSTANCENAME.$NAME": { + StateVersion: 0, + Attributes: map[string]string{ + "name": "tf-user", + "instance": "tf-instance", + }, + Expected: map[string]string{ + "name": "tf-user", + "instance": "tf-instance", + }, + Meta: &Config{}, + ID: "tf-user", + ExpectedID: "tf-instance/tf-user", + }, + } + + for tn, tc := range cases { + is := &terraform.InstanceState{ + ID: tc.ID, + Attributes: tc.Attributes, + } + is, err := resourceSqlUserMigrateState( + tc.StateVersion, is, tc.Meta) + + if err != nil { + t.Fatalf("bad: %s, err: %#v", tn, err) + } + + if is.ID != tc.ExpectedID { + t.Fatalf("bad ID.\n\n expected: %s\n got: %s", tc.ExpectedID, is.ID) + } + + for k, v := range tc.Expected { + if is.Attributes[k] != v { + t.Fatalf( + "bad: %s\n\n expected: %#v -> %#v\n got: %#v -> %#v\n in: %#v", + tn, k, v, k, is.Attributes[k], is.Attributes) + } + } + } +} + +func TestSqlUserMigrateState_empty(t *testing.T) { + var is *terraform.InstanceState + var meta *Config + + // should handle nil + is, err := resourceSqlUserMigrateState(0, is, meta) + + if err != nil { + t.Fatalf("err: %#v", err) + } + if is != nil { + t.Fatalf("expected nil instancestate, got: %#v", is) + } + + // should handle non-nil but empty + is = &terraform.InstanceState{} + is, err = resourceSqlUserMigrateState(0, is, meta) + + if err != nil { + t.Fatalf("err: %#v", err) + } +} diff --git a/website/source/docs/import/importability.html.md b/website/source/docs/import/importability.html.md index 1adb0134c..1c3ad9b52 100644 --- a/website/source/docs/import/importability.html.md +++ b/website/source/docs/import/importability.html.md @@ -153,6 +153,7 @@ To make a resource importable, please see the * google_compute_target_pool * google_dns_managed_zone * google_project +* google_sql_user ### OpenStack diff --git a/website/source/docs/providers/google/r/sql_user.html.markdown b/website/source/docs/providers/google/r/sql_user.html.markdown index a486a6b9e..0b35fa1b8 100644 --- a/website/source/docs/providers/google/r/sql_user.html.markdown +++ b/website/source/docs/providers/google/r/sql_user.html.markdown @@ -11,7 +11,8 @@ description: |- Creates a new Google SQL User on a Google SQL User Instance. For more information, see the [official documentation](https://cloud.google.com/sql/), or the [JSON API](https://cloud.google.com/sql/docs/admin-api/v1beta4/users). ~> **Note:** All arguments including the username and password will be stored in the raw state as plain-text. -[Read more about sensitive data in state](/docs/state/sensitive-data.html). +[Read more about sensitive data in state](/docs/state/sensitive-data.html). Passwords will not be retrieved when running +"terraform import". ## Example Usage @@ -57,3 +58,17 @@ The following arguments are supported: ## Attributes Reference Only the arguments listed above are exposed as attributes. + +## Import Format + +Importing an SQL user is formatted as: + +```bash +terraform import google_sql_user.$RESOURCENAME $INSTANCENAME/$SQLUSERNAME +``` + +For example, the sample at the top of this page could be imported with: + +```bash +terraform import google_sql_user.users master-instance/me +``` \ No newline at end of file