From 5197e0fbcdd2ba64f9fd6c91b6e3533e4bcf4590 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Mon, 13 Aug 2018 12:48:21 +0100 Subject: [PATCH] resource/github_repository_webhook: Avoid spurious diff --- github/migrate_github_repository_webhook.go | 55 ++++++++++++ .../migrate_github_repository_webhook_test.go | 38 +++++++++ github/resource_github_repository_webhook.go | 43 +++++++++- ...resource_github_repository_webhook_test.go | 83 +++++++++++++++++++ 4 files changed, 216 insertions(+), 3 deletions(-) create mode 100644 github/migrate_github_repository_webhook.go create mode 100644 github/migrate_github_repository_webhook_test.go diff --git a/github/migrate_github_repository_webhook.go b/github/migrate_github_repository_webhook.go new file mode 100644 index 0000000000..7993c6b15a --- /dev/null +++ b/github/migrate_github_repository_webhook.go @@ -0,0 +1,55 @@ +package github + +import ( + "fmt" + "log" + "strings" + + "github.com/hashicorp/terraform/terraform" +) + +func resourceGithubRepositoryWebhookMigrateState(v int, is *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) { + switch v { + case 0: + log.Println("[INFO] Found GitHub Repository Webhook State v0; migrating to v1") + return migrateGithubRepositoryWebhookStateV0toV1(is) + default: + return is, fmt.Errorf("Unexpected schema version: %d", v) + } +} + +func migrateGithubRepositoryWebhookStateV0toV1(is *terraform.InstanceState) (*terraform.InstanceState, error) { + if is.Empty() { + log.Println("[DEBUG] Empty InstanceState; nothing to migrate.") + return is, nil + } + + log.Printf("[DEBUG] GitHub Repository Webhook Attributes before migration: %#v", is.Attributes) + + prefix := "configuration." + + delete(is.Attributes, prefix+"%") + + // Read & delete old keys + oldKeys := make(map[string]string, 0) + for k, v := range is.Attributes { + if strings.HasPrefix(k, prefix) { + oldKeys[k] = v + + // Delete old keys + delete(is.Attributes, k) + } + } + + // Write new keys + for k, v := range oldKeys { + newKey := "configuration.0." + strings.TrimPrefix(k, prefix) + is.Attributes[newKey] = v + } + + is.Attributes[prefix+"#"] = "1" + + log.Printf("[DEBUG] GitHub Repository Webhook Attributes after State Migration: %#v", is.Attributes) + + return is, nil +} diff --git a/github/migrate_github_repository_webhook_test.go b/github/migrate_github_repository_webhook_test.go new file mode 100644 index 0000000000..84aa7ee869 --- /dev/null +++ b/github/migrate_github_repository_webhook_test.go @@ -0,0 +1,38 @@ +package github + +import ( + "reflect" + "testing" + + "github.com/hashicorp/terraform/terraform" +) + +func TestMigrateGithubRepositoryWebhookStateV0toV1(t *testing.T) { + oldAttributes := map[string]string{ + "configuration.%": "4", + "configuration.content_type": "form", + "configuration.insecure_ssl": "0", + "configuration.secret": "blablah", + "configuration.url": "https://google.co.uk/", + } + + newState, err := migrateGithubRepositoryWebhookStateV0toV1(&terraform.InstanceState{ + ID: "nonempty", + Attributes: oldAttributes, + }) + if err != nil { + t.Fatal(err) + } + + expectedAttributes := map[string]string{ + "configuration.#": "1", + "configuration.0.content_type": "form", + "configuration.0.insecure_ssl": "0", + "configuration.0.secret": "blablah", + "configuration.0.url": "https://google.co.uk/", + } + if !reflect.DeepEqual(newState.Attributes, expectedAttributes) { + t.Fatalf("Expected attributes:\n%#v\n\nGiven:\n%#v\n", + expectedAttributes, newState.Attributes) + } +} diff --git a/github/resource_github_repository_webhook.go b/github/resource_github_repository_webhook.go index 70925e9cb6..386257cf6e 100644 --- a/github/resource_github_repository_webhook.go +++ b/github/resource_github_repository_webhook.go @@ -28,6 +28,9 @@ func resourceGithubRepositoryWebhook() *schema.Resource { }, }, + SchemaVersion: 1, + MigrateState: resourceGithubRepositoryWebhookMigrateState, + Schema: map[string]*schema.Schema{ "name": { Type: schema.TypeString, @@ -46,8 +49,38 @@ func resourceGithubRepositoryWebhook() *schema.Resource { Set: schema.HashString, }, "configuration": { - Type: schema.TypeMap, + Type: schema.TypeList, + MaxItems: 1, Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "url": { + Type: schema.TypeString, + Required: true, + }, + "content_type": { + Type: schema.TypeString, + Optional: true, + }, + "secret": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + DiffSuppressFunc: func(k, oldV, newV string, d *schema.ResourceData) bool { + maskedSecret := "********" + if oldV == maskedSecret { + return true + } + + return oldV == newV + }, + }, + "insecure_ssl": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, }, "url": { Type: schema.TypeString, @@ -77,7 +110,11 @@ func resourceGithubRepositoryWebhookObject(d *schema.ResourceData) *github.Hook URL: &url, Events: events, Active: &active, - Config: d.Get("configuration").(map[string]interface{}), + } + + config := d.Get("configuration").([]interface{}) + if len(config) > 0 { + hook.Config = config[0].(map[string]interface{}) } return hook @@ -115,7 +152,7 @@ func resourceGithubRepositoryWebhookRead(d *schema.ResourceData, meta interface{ d.Set("url", hook.URL) d.Set("active", hook.Active) d.Set("events", hook.Events) - d.Set("configuration", hook.Config) + d.Set("configuration", []interface{}{hook.Config}) return nil } diff --git a/github/resource_github_repository_webhook_test.go b/github/resource_github_repository_webhook_test.go index e2e4b99926..1b9109a1a5 100644 --- a/github/resource_github_repository_webhook_test.go +++ b/github/resource_github_repository_webhook_test.go @@ -59,6 +59,36 @@ func TestAccGithubRepositoryWebhook_basic(t *testing.T) { }) } +func TestAccGithubRepositoryWebhook_secret(t *testing.T) { + randString := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + var hook github.Hook + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGithubRepositoryWebhookDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGithubRepositoryWebhookConfig_secret(randString), + Check: resource.ComposeTestCheckFunc( + testAccCheckGithubRepositoryWebhookExists("github_repository_webhook.foo", fmt.Sprintf("foo-%s", randString), &hook), + testAccCheckGithubRepositoryWebhookAttributes(&hook, &testAccGithubRepositoryWebhookExpectedAttributes{ + Name: "web", + Events: []string{"pull_request"}, + Configuration: map[string]interface{}{ + "url": "https://www.terraform.io/webhook", + "content_type": "json", + "secret": "********", + "insecure_ssl": "0", + }, + Active: true, + }), + ), + }, + }, + }) +} + func TestAccGithubRepositoryWebhook_importBasic(t *testing.T) { randString := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) @@ -80,6 +110,27 @@ func TestAccGithubRepositoryWebhook_importBasic(t *testing.T) { }) } +func TestAccGithubRepositoryWebhook_importSecret(t *testing.T) { + randString := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGithubRepositoryDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGithubRepositoryWebhookConfig_secret(randString), + }, + { + ResourceName: "github_repository_webhook.foo", + ImportState: true, + ImportStateVerify: true, + ImportStateIdPrefix: fmt.Sprintf("foo-%s/", randString), + }, + }, + }) +} + func testAccCheckGithubRepositoryWebhookExists(n string, repoName string, hook *github.Hook) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -196,6 +247,38 @@ func testAccGithubRepositoryWebhookConfig(randString string) string { `, randString, randString) } +func testAccGithubRepositoryWebhookConfig_secret(randString string) string { + return fmt.Sprintf(` +resource "github_repository" "foo" { + name = "foo-%s" + description = "Terraform acceptance tests" + homepage_url = "http://example.com/" + + # So that acceptance tests can be run in a github organization + # with no billing + private = false + + has_issues = true + has_wiki = true + has_downloads = true +} + +resource "github_repository_webhook" "foo" { + repository = "${github_repository.foo.name}" + + name = "web" + configuration { + url = "https://www.terraform.io/webhook" + content_type = "json" + secret = "RandomSecretString" + insecure_ssl = false + } + + events = ["pull_request"] +} +`, randString) +} + func testAccGithubRepositoryWebhookUpdateConfig(randString string) string { return fmt.Sprintf(` resource "github_repository" "foo" {