diff --git a/.changelog/4931.txt b/.changelog/4931.txt new file mode 100644 index 00000000000..da0c5677723 --- /dev/null +++ b/.changelog/4931.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +cloudbuild: Added `pubsub_config` and `webhook_config` parameter to `google_cloudbuild_trigger`. +``` diff --git a/google/resource_cloudbuild_trigger.go b/google/resource_cloudbuild_trigger.go index 7af9be8d035..2a2080433e8 100644 --- a/google/resource_cloudbuild_trigger.go +++ b/google/resource_cloudbuild_trigger.go @@ -648,7 +648,7 @@ Default time is ten minutes (600s).`, Optional: true, Description: `Describes the configuration of a trigger that creates a build whenever a GitHub event is received. -One of 'trigger_template' or 'github' must be provided.`, +One of 'trigger_template', 'github', 'pubsub_config' or 'webhook_config' must be provided.`, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -721,7 +721,7 @@ https://github.com/googlecloudplatform/cloud-builders is "googlecloudplatform".` }, }, }, - ExactlyOneOf: []string{"trigger_template", "github"}, + ExactlyOneOf: []string{"trigger_template", "github", "pubsub_config", "webhook_config"}, }, "ignored_files": { Type: schema.TypeList, @@ -763,6 +763,41 @@ a build.`, Optional: true, Description: `Name of the trigger. Must be unique within the project.`, }, + "pubsub_config": { + Type: schema.TypeList, + Optional: true, + Description: `PubsubConfig describes the configuration of a trigger that creates +a build whenever a Pub/Sub message is published. + +One of 'trigger_template', 'github', 'pubsub_config' or 'webhook_config' must be provided.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "topic": { + Type: schema.TypeString, + Required: true, + Description: `The name of the topic from which this subscription is receiving messages.`, + }, + "service_account_email": { + Type: schema.TypeString, + Optional: true, + Description: `Service account that will make the push request.`, + }, + "state": { + Type: schema.TypeString, + Computed: true, + Description: `Potential issues with the underlying Pub/Sub subscription configuration. +Only populated on get requests.`, + }, + "subscription": { + Type: schema.TypeString, + Computed: true, + Description: `Output only. Name of the subscription.`, + }, + }, + }, + ExactlyOneOf: []string{"trigger_template", "github", "pubsub_config", "webhook_config"}, + }, "substitutions": { Type: schema.TypeMap, Optional: true, @@ -786,7 +821,7 @@ Branch and tag names in trigger templates are interpreted as regular expressions. Any branch or tag change that matches that regular expression will trigger a build. -One of 'trigger_template' or 'github' must be provided.`, +One of 'trigger_template', 'github', 'pubsub_config' or 'webhook_config' must be provided.`, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -839,6 +874,32 @@ This field is a regular expression.`, }, }, }, + ExactlyOneOf: []string{"trigger_template", "github", "pubsub_config", "webhook_config"}, + }, + "webhook_config": { + Type: schema.TypeList, + Optional: true, + Description: `WebhookConfig describes the configuration of a trigger that creates +a build whenever a webhook is sent to a trigger's webhook URL. + +One of 'trigger_template', 'github', 'pubsub_config' or 'webhook_config' must be provided.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "secret": { + Type: schema.TypeString, + Required: true, + Description: `Resource name for the secret required as a URL parameter.`, + }, + "state": { + Type: schema.TypeString, + Computed: true, + Description: `Potential issues with the underlying Pub/Sub subscription configuration. +Only populated on get requests.`, + }, + }, + }, + ExactlyOneOf: []string{"trigger_template", "github", "pubsub_config", "webhook_config"}, }, "create_time": { Type: schema.TypeString, @@ -929,6 +990,18 @@ func resourceCloudBuildTriggerCreate(d *schema.ResourceData, meta interface{}) e } else if v, ok := d.GetOkExists("github"); !isEmptyValue(reflect.ValueOf(githubProp)) && (ok || !reflect.DeepEqual(v, githubProp)) { obj["github"] = githubProp } + pubsubConfigProp, err := expandCloudBuildTriggerPubsubConfig(d.Get("pubsub_config"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("pubsub_config"); !isEmptyValue(reflect.ValueOf(pubsubConfigProp)) && (ok || !reflect.DeepEqual(v, pubsubConfigProp)) { + obj["pubsubConfig"] = pubsubConfigProp + } + webhookConfigProp, err := expandCloudBuildTriggerWebhookConfig(d.Get("webhook_config"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("webhook_config"); !isEmptyValue(reflect.ValueOf(webhookConfigProp)) && (ok || !reflect.DeepEqual(v, webhookConfigProp)) { + obj["webhookConfig"] = webhookConfigProp + } buildProp, err := expandCloudBuildTriggerBuild(d.Get("build"), d, config) if err != nil { return err @@ -1059,6 +1132,12 @@ func resourceCloudBuildTriggerRead(d *schema.ResourceData, meta interface{}) err if err := d.Set("github", flattenCloudBuildTriggerGithub(res["github"], d, config)); err != nil { return fmt.Errorf("Error reading Trigger: %s", err) } + if err := d.Set("pubsub_config", flattenCloudBuildTriggerPubsubConfig(res["pubsubConfig"], d, config)); err != nil { + return fmt.Errorf("Error reading Trigger: %s", err) + } + if err := d.Set("webhook_config", flattenCloudBuildTriggerWebhookConfig(res["webhookConfig"], d, config)); err != nil { + return fmt.Errorf("Error reading Trigger: %s", err) + } if err := d.Set("build", flattenCloudBuildTriggerBuild(res["build"], d, config)); err != nil { return fmt.Errorf("Error reading Trigger: %s", err) } @@ -1142,6 +1221,18 @@ func resourceCloudBuildTriggerUpdate(d *schema.ResourceData, meta interface{}) e } else if v, ok := d.GetOkExists("github"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, githubProp)) { obj["github"] = githubProp } + pubsubConfigProp, err := expandCloudBuildTriggerPubsubConfig(d.Get("pubsub_config"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("pubsub_config"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, pubsubConfigProp)) { + obj["pubsubConfig"] = pubsubConfigProp + } + webhookConfigProp, err := expandCloudBuildTriggerWebhookConfig(d.Get("webhook_config"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("webhook_config"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, webhookConfigProp)) { + obj["webhookConfig"] = webhookConfigProp + } buildProp, err := expandCloudBuildTriggerBuild(d.Get("build"), d, config) if err != nil { return err @@ -1408,6 +1499,64 @@ func flattenCloudBuildTriggerGithubPushTag(v interface{}, d *schema.ResourceData return v } +func flattenCloudBuildTriggerPubsubConfig(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["subscription"] = + flattenCloudBuildTriggerPubsubConfigSubscription(original["subscription"], d, config) + transformed["topic"] = + flattenCloudBuildTriggerPubsubConfigTopic(original["topic"], d, config) + transformed["service_account_email"] = + flattenCloudBuildTriggerPubsubConfigServiceAccountEmail(original["service_account_email"], d, config) + transformed["state"] = + flattenCloudBuildTriggerPubsubConfigState(original["state"], d, config) + return []interface{}{transformed} +} +func flattenCloudBuildTriggerPubsubConfigSubscription(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudBuildTriggerPubsubConfigTopic(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudBuildTriggerPubsubConfigServiceAccountEmail(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudBuildTriggerPubsubConfigState(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudBuildTriggerWebhookConfig(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["secret"] = + flattenCloudBuildTriggerWebhookConfigSecret(original["secret"], d, config) + transformed["state"] = + flattenCloudBuildTriggerWebhookConfigState(original["state"], d, config) + return []interface{}{transformed} +} +func flattenCloudBuildTriggerWebhookConfigSecret(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudBuildTriggerWebhookConfigState(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + func flattenCloudBuildTriggerBuild(v interface{}, d *schema.ResourceData, config *Config) interface{} { if v == nil { return nil @@ -2142,6 +2291,96 @@ func expandCloudBuildTriggerGithubPushTag(v interface{}, d TerraformResourceData return v, nil } +func expandCloudBuildTriggerPubsubConfig(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedSubscription, err := expandCloudBuildTriggerPubsubConfigSubscription(original["subscription"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedSubscription); val.IsValid() && !isEmptyValue(val) { + transformed["subscription"] = transformedSubscription + } + + transformedTopic, err := expandCloudBuildTriggerPubsubConfigTopic(original["topic"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedTopic); val.IsValid() && !isEmptyValue(val) { + transformed["topic"] = transformedTopic + } + + transformedServiceAccountEmail, err := expandCloudBuildTriggerPubsubConfigServiceAccountEmail(original["service_account_email"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedServiceAccountEmail); val.IsValid() && !isEmptyValue(val) { + transformed["service_account_email"] = transformedServiceAccountEmail + } + + transformedState, err := expandCloudBuildTriggerPubsubConfigState(original["state"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedState); val.IsValid() && !isEmptyValue(val) { + transformed["state"] = transformedState + } + + return transformed, nil +} + +func expandCloudBuildTriggerPubsubConfigSubscription(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudBuildTriggerPubsubConfigTopic(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudBuildTriggerPubsubConfigServiceAccountEmail(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudBuildTriggerPubsubConfigState(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudBuildTriggerWebhookConfig(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedSecret, err := expandCloudBuildTriggerWebhookConfigSecret(original["secret"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedSecret); val.IsValid() && !isEmptyValue(val) { + transformed["secret"] = transformedSecret + } + + transformedState, err := expandCloudBuildTriggerWebhookConfigState(original["state"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedState); val.IsValid() && !isEmptyValue(val) { + transformed["state"] = transformedState + } + + return transformed, nil +} + +func expandCloudBuildTriggerWebhookConfigSecret(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudBuildTriggerWebhookConfigState(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + func expandCloudBuildTriggerBuild(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { l := v.([]interface{}) if len(l) == 0 || l[0] == nil { diff --git a/google/resource_cloudbuild_trigger_test.go b/google/resource_cloudbuild_trigger_test.go index 8fb8596a044..986516e5304 100644 --- a/google/resource_cloudbuild_trigger_test.go +++ b/google/resource_cloudbuild_trigger_test.go @@ -37,6 +37,64 @@ func TestAccCloudBuildTrigger_basic(t *testing.T) { }) } +func TestAccCloudBuildTrigger_pubsub_config(t *testing.T) { + t.Parallel() + name := fmt.Sprintf("tf-test-%d", randInt(t)) + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudBuildTriggerDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccCloudBuildTrigger_pubsub_config(name), + }, + { + ResourceName: "google_cloudbuild_trigger.build_trigger", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccCloudBuildTrigger_pubsub_config_update(name), + }, + { + ResourceName: "google_cloudbuild_trigger.build_trigger", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccCloudBuildTrigger_webhook_config(t *testing.T) { + t.Parallel() + name := fmt.Sprintf("tf-test-%d", randInt(t)) + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudBuildTriggerDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccCloudBuildTrigger_webhook_config(name), + }, + { + ResourceName: "google_cloudbuild_trigger.build_trigger", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccCloudBuildTrigger_webhook_config_update(name), + }, + { + ResourceName: "google_cloudbuild_trigger.build_trigger", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccCloudBuildTrigger_customizeDiffTimeoutSum(t *testing.T) { t.Parallel() @@ -281,6 +339,192 @@ resource "google_cloudbuild_trigger" "build_trigger" { `, name) } +func testAccCloudBuildTrigger_pubsub_config(name string) string { + return fmt.Sprintf(` +resource "google_pubsub_topic" "build-trigger" { + name = "topic-name" +} + +resource "google_cloudbuild_trigger" "build_trigger" { + name = "%s" + description = "acceptance test build trigger" + pubsub_config { + topic = "${google_pubsub_topic.build-trigger.id}" + } + build { + tags = ["team-a", "service-b"] + timeout = "1800s" + step { + name = "gcr.io/cloud-builders/gsutil" + args = ["cp", "gs://mybucket/remotefile.zip", "localfile.zip"] + timeout = "300s" + } + } + depends_on = [ + google_pubsub_topic.build-trigger + ] +} +`, name) +} + +func testAccCloudBuildTrigger_pubsub_config_update(name string) string { + return fmt.Sprintf(` +resource "google_pubsub_topic" "build-trigger" { + name = "topic-name" +} + +resource "google_cloudbuild_trigger" "build_trigger" { + name = "%s" + description = "acceptance test build trigger updated" + pubsub_config { + topic = "${google_pubsub_topic.build-trigger.id}" + } + build { + tags = ["team-a", "service-b"] + timeout = "1800s" + step { + name = "gcr.io/cloud-builders/gsutil" + args = ["cp", "gs://mybucket/remotefile.zip", "localfile.zip"] + timeout = "300s" + } + } + depends_on = [ + google_pubsub_topic.build-trigger + ] +} +`, name) +} + +func testAccCloudBuildTrigger_webhook_config(name string) string { + return fmt.Sprintf(` +resource "google_secret_manager_secret" "webhook_trigger_secret_key" { + secret_id = "webhook_trigger-secret-key" + + replication { + user_managed { + replicas { + location = "us-central1" + } + } + } +} + +resource "google_secret_manager_secret_version" "webhook_trigger_secret_key_data" { + secret = google_secret_manager_secret.webhook_trigger_secret_key.id + + secret_data = "secretkeygoeshere" +} + +data "google_project" "project" {} + +data "google_iam_policy" "secret_accessor" { + binding { + role = "roles/secretmanager.secretAccessor" + members = [ + "serviceAccount:service-${data.google_project.project.number}@gcp-sa-cloudbuild.iam.gserviceaccount.com", + ] + } +} + +resource "google_secret_manager_secret_iam_policy" "policy" { + project = google_secret_manager_secret.webhook_trigger_secret_key.project + secret_id = google_secret_manager_secret.webhook_trigger_secret_key.secret_id + policy_data = data.google_iam_policy.secret_accessor.policy_data +} + +resource "google_cloudbuild_trigger" "build_trigger" { + name = "%s" + + webhook_config { + secret = "${google_secret_manager_secret_version.webhook_trigger_secret_key_data.id}" + } + + build { + step { + name = "ubuntu" + args = [ + "-c", + <