diff --git a/google-beta/iam.go b/google-beta/iam.go index e80d907c78..4c16988b8f 100644 --- a/google-beta/iam.go +++ b/google-beta/iam.go @@ -123,6 +123,25 @@ func iamPolicyReadModifyWrite(updater ResourceIamUpdater, modify iamPolicyModify return nil } +// Takes a single binding and will either overwrite the same role in a list or append it to the end +func overwriteBinding(bindings []*cloudresourcemanager.Binding, overwrite *cloudresourcemanager.Binding) []*cloudresourcemanager.Binding { + var found bool + + for i, b := range bindings { + if b.Role == overwrite.Role { + bindings[i] = overwrite + found = true + break + } + } + + if !found { + bindings = append(bindings, overwrite) + } + + return bindings +} + // Merge multiple Bindings such that Bindings with the same Role result in // a single Binding with combined Members func mergeBindings(bindings []*cloudresourcemanager.Binding) []*cloudresourcemanager.Binding { diff --git a/google-beta/provider.go b/google-beta/provider.go index 73ff4c6d22..2478e17527 100644 --- a/google-beta/provider.go +++ b/google-beta/provider.go @@ -123,7 +123,6 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { // end beta-only products GeneratedAppengineResourcesMap, GeneratedComputeResourcesMap, - GeneratedCloudbuildResourcesMap, GeneratedDnsResourcesMap, GeneratedRedisResourcesMap, GeneratedResourceManagerResourcesMap, @@ -139,6 +138,7 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { "google_billing_account_iam_binding": ResourceIamBindingWithImport(IamBillingAccountSchema, NewBillingAccountIamUpdater, BillingAccountIdParseFunc), "google_billing_account_iam_member": ResourceIamMemberWithImport(IamBillingAccountSchema, NewBillingAccountIamUpdater, BillingAccountIdParseFunc), "google_billing_account_iam_policy": ResourceIamPolicyWithImport(IamBillingAccountSchema, NewBillingAccountIamUpdater, BillingAccountIdParseFunc), + "google_cloudbuild_trigger": resourceCloudBuildTrigger(), "google_cloudfunctions_function": resourceCloudFunctionsFunction(), "google_cloudiot_registry": resourceCloudIoTRegistry(), "google_composer_environment": resourceComposerEnvironment(), diff --git a/google-beta/provider_cloudbuild_gen.go b/google-beta/provider_cloudbuild_gen.go deleted file mode 100644 index d9ea14517f..0000000000 --- a/google-beta/provider_cloudbuild_gen.go +++ /dev/null @@ -1,21 +0,0 @@ -// ---------------------------------------------------------------------------- -// -// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** -// -// ---------------------------------------------------------------------------- -// -// This file is automatically generated by Magic Modules and manual -// changes will be clobbered when the file is regenerated. -// -// Please read more about how to change this file in -// .github/CONTRIBUTING.md. -// -// ---------------------------------------------------------------------------- - -package google - -import "github.com/hashicorp/terraform/helper/schema" - -var GeneratedCloudbuildResourcesMap = map[string]*schema.Resource{ - "google_cloudbuild_trigger": resourceCloudbuildTrigger(), -} diff --git a/google-beta/resource_cloudbuild_build_trigger.go b/google-beta/resource_cloudbuild_build_trigger.go new file mode 100644 index 0000000000..3af669fc7b --- /dev/null +++ b/google-beta/resource_cloudbuild_build_trigger.go @@ -0,0 +1,386 @@ +// Package google - implement CRUD operations for Container Registry Build Triggers +// https://cloud.google.com/container-builder/docs/api/reference/rest/v1/projects.triggers#BuildTrigger +package google + +import ( + "encoding/json" + "fmt" + "log" + "strings" + "time" + + "github.com/hashicorp/terraform/helper/schema" + "google.golang.org/api/cloudbuild/v1" +) + +func resourceCloudBuildTrigger() *schema.Resource { + return &schema.Resource{ + Create: resourceCloudbuildBuildTriggerCreate, + Read: resourceCloudbuildBuildTriggerRead, + Update: resourceCloudbuildBuildTriggerUpdate, + Delete: resourceCloudbuildBuildTriggerDelete, + Importer: &schema.ResourceImporter{ + State: resourceCloudBuildTriggerImportState, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(5 * time.Minute), + Delete: schema.DefaultTimeout(3 * time.Minute), + }, + + SchemaVersion: 1, + + Schema: map[string]*schema.Schema{ + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "filename": { + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"build"}, + }, + "build": { + Type: schema.TypeList, + Description: "Contents of the build template.", + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "images": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "step": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + }, + "args": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "tags": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "substitutions": { + Optional: true, + Type: schema.TypeMap, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "included_files": { + Optional: true, + Type: schema.TypeList, + MaxItems: 50, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "ignored_files": { + Optional: true, + Type: schema.TypeList, + MaxItems: 50, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "trigger_template": { + Optional: true, + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "branch_name": { + Type: schema.TypeString, + Optional: true, + }, + "commit_sha": { + Type: schema.TypeString, + Optional: true, + }, + "dir": { + Type: schema.TypeString, + Optional: true, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "repo_name": { + Type: schema.TypeString, + Optional: true, + }, + "tag_name": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "trigger_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceCloudbuildBuildTriggerCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + buildTrigger, err := expandCloudbuildBuildTrigger(d, meta) + if err != nil { + return err + } + + buildTrigger.IgnoredFiles = expandStringSlice(d, "ignored_files") + buildTrigger.IncludedFiles = expandStringSlice(d, "included_files") + + tstr, err := json.Marshal(buildTrigger) + if err != nil { + return err + } + log.Printf("[INFO] build trigger request: %s", string(tstr)) + trigger, err := config.clientBuild.Projects.Triggers.Create(project, buildTrigger).Do() + if err != nil { + return fmt.Errorf("Error creating build trigger: %s", err) + } + + d.SetId(trigger.Id) + + return resourceCloudbuildBuildTriggerRead(d, meta) +} + +func resourceCloudbuildBuildTriggerRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + ID := d.Id() + buildTrigger, err := config.clientBuild.Projects.Triggers.Get(project, ID).Do() + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("Cloudbuild Trigger %q", ID)) + } + + d.Set("description", buildTrigger.Description) + d.Set("substitutions", buildTrigger.Substitutions) + d.Set("ignored_files", buildTrigger.IgnoredFiles) + d.Set("included_files", buildTrigger.IncludedFiles) + d.Set("trigger_id", buildTrigger.Id) + + if buildTrigger.TriggerTemplate != nil { + d.Set("trigger_template", flattenCloudbuildBuildTriggerTemplate(d, config, buildTrigger.TriggerTemplate)) + } + + if buildTrigger.Filename != "" { + d.Set("filename", buildTrigger.Filename) + } else if buildTrigger.Build != nil { + d.Set("build", flattenCloudbuildBuildTriggerBuild(d, config, buildTrigger.Build)) + } + + return nil +} + +func resourceCloudbuildBuildTriggerUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + buildTrigger, err := expandCloudbuildBuildTrigger(d, meta) + if err != nil { + return err + } + buildTrigger.Id = d.Get("trigger_id").(string) + + id := d.Id() + + log.Printf("[INFO] Updating Cloud Build Trigger: %s", id) + + if _, err = config.clientBuild.Projects.Triggers.Patch(project, id, buildTrigger).Do(); err != nil { + return err + } + + return resourceCloudbuildBuildTriggerRead(d, meta) +} + +func expandCloudbuildBuildTrigger(d *schema.ResourceData, meta interface{}) (*cloudbuild.BuildTrigger, error) { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return nil, err + } + + t := &cloudbuild.BuildTrigger{} + + if v, ok := d.GetOk("description"); ok { + t.Description = v.(string) + } + + if v, ok := d.GetOk("filename"); ok { + t.Filename = v.(string) + } else { + t.Build = expandCloudbuildBuildTriggerBuild(d) + } + + t.Substitutions = expandStringMap(d, "substitutions") + t.TriggerTemplate = expandCloudbuildBuildTriggerTemplate(d, project) + + return t, nil +} + +func expandCloudbuildBuildTriggerTemplate(d *schema.ResourceData, project string) *cloudbuild.RepoSource { + if d.Get("trigger_template.#").(int) == 0 { + return nil + } + tmpl := &cloudbuild.RepoSource{} + if v, ok := d.GetOk("trigger_template.0.project"); ok { + tmpl.ProjectId = v.(string) + } else { + tmpl.ProjectId = project + } + if v, ok := d.GetOk("trigger_template.0.branch_name"); ok { + tmpl.BranchName = v.(string) + } + if v, ok := d.GetOk("trigger_template.0.commit_sha"); ok { + tmpl.CommitSha = v.(string) + } + if v, ok := d.GetOk("trigger_template.0.dir"); ok { + tmpl.Dir = v.(string) + } + if v, ok := d.GetOk("trigger_template.0.repo_name"); ok { + tmpl.RepoName = v.(string) + } + if v, ok := d.GetOk("trigger_template.0.tag_name"); ok { + tmpl.TagName = v.(string) + } + return tmpl +} + +func flattenCloudbuildBuildTriggerTemplate(d *schema.ResourceData, config *Config, t *cloudbuild.RepoSource) []map[string]interface{} { + flattened := make([]map[string]interface{}, 1) + + flattened[0] = map[string]interface{}{ + "branch_name": t.BranchName, + "commit_sha": t.CommitSha, + "dir": t.Dir, + "project": t.ProjectId, + "repo_name": t.RepoName, + "tag_name": t.TagName, + } + + return flattened +} + +func expandCloudbuildBuildTriggerBuild(d *schema.ResourceData) *cloudbuild.Build { + if d.Get("build.#").(int) == 0 { + return nil + } + + build := &cloudbuild.Build{} + if v, ok := d.GetOk("build.0.images"); ok { + build.Images = convertStringArr(v.([]interface{})) + } + if v, ok := d.GetOk("build.0.tags"); ok { + build.Tags = convertStringArr(v.([]interface{})) + } + stepCount := d.Get("build.0.step.#").(int) + build.Steps = make([]*cloudbuild.BuildStep, 0, stepCount) + for s := 0; s < stepCount; s++ { + step := &cloudbuild.BuildStep{ + Name: d.Get(fmt.Sprintf("build.0.step.%d.name", s)).(string), + } + if v, ok := d.GetOk(fmt.Sprintf("build.0.step.%d.args", s)); ok { + step.Args = strings.Split(v.(string), " ") + } + build.Steps = append(build.Steps, step) + } + return build +} + +func flattenCloudbuildBuildTriggerBuild(d *schema.ResourceData, config *Config, b *cloudbuild.Build) []map[string]interface{} { + flattened := make([]map[string]interface{}, 1) + + flattened[0] = map[string]interface{}{} + + if b.Images != nil { + flattened[0]["images"] = convertStringArrToInterface(b.Images) + } + if b.Tags != nil { + flattened[0]["tags"] = convertStringArrToInterface(b.Tags) + } + if b.Steps != nil { + steps := make([]map[string]interface{}, len(b.Steps)) + for i, step := range b.Steps { + steps[i] = map[string]interface{}{} + steps[i]["name"] = step.Name + steps[i]["args"] = strings.Join(step.Args, " ") + } + flattened[0]["step"] = steps + } + + return flattened +} + +func resourceCloudbuildBuildTriggerDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + // Delete the build trigger + log.Printf("[DEBUG] build trigger delete request") + _, err = config.clientBuild.Projects.Triggers.Delete( + project, d.Id()).Do() + if err != nil { + return fmt.Errorf("Error deleting build trigger: %s", err) + } + + d.SetId("") + return nil +} + +func resourceCloudBuildTriggerImportState(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + parts := strings.Split(d.Id(), "/") + + if len(parts) == 1 { + return []*schema.ResourceData{d}, nil + } else if len(parts) == 2 { + d.Set("project", parts[0]) + d.SetId(parts[1]) + return []*schema.ResourceData{d}, nil + } else { + return nil, fmt.Errorf("Invalid import id %q. Expecting {trigger_name} or {project}/{trigger_name}", d.Id()) + } +} diff --git a/google-beta/resource_cloudbuild_build_trigger_test.go b/google-beta/resource_cloudbuild_build_trigger_test.go index 0cbda174a4..67b9201af2 100644 --- a/google-beta/resource_cloudbuild_build_trigger_test.go +++ b/google-beta/resource_cloudbuild_build_trigger_test.go @@ -4,32 +4,50 @@ import ( "fmt" "testing" + "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + cloudbuild "google.golang.org/api/cloudbuild/v1" ) func TestAccCloudBuildTrigger_basic(t *testing.T) { t.Parallel() + projectID := "terraform-" + acctest.RandString(10) + projectOrg := getTestOrgFromEnv(t) + projectBillingAccount := getTestBillingAccountFromEnv(t) + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, - CheckDestroy: testAccCheckCloudbuildTriggerDestroy, + CheckDestroy: testAccCheckGoogleCloudBuildTriggerVersionsDestroyed, Steps: []resource.TestStep{ { - Config: testGoogleCloudBuildTrigger_basic(), + Config: testGoogleCloudBuildTrigger_basic(projectID, projectOrg, projectBillingAccount), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleCloudBuildTriggerExists("google_cloudbuild_trigger.build_trigger"), + ), + }, + { + ResourceName: "google_cloudbuild_trigger.build_trigger", + ImportState: true, + ImportStateVerify: true, + ImportStateIdPrefix: fmt.Sprintf("%s/", projectID), }, { - ResourceName: "google_cloudbuild_trigger.build_trigger", - ImportState: true, - ImportStateVerify: true, + Config: testGoogleCloudBuildTrigger_updated(projectID, projectOrg, projectBillingAccount), }, { - Config: testGoogleCloudBuildTrigger_updated(), + ResourceName: "google_cloudbuild_trigger.build_trigger", + ImportState: true, + ImportStateVerify: true, + ImportStateIdPrefix: fmt.Sprintf("%s/", projectID), }, { - ResourceName: "google_cloudbuild_trigger.build_trigger", - ImportState: true, - ImportStateVerify: true, + Config: testGoogleCloudBuildTrigger_removed(projectID, projectOrg, projectBillingAccount), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleCloudBuildTriggerWasRemovedFromState("google_cloudbuild_trigger.build_trigger"), + ), }, }, }) @@ -38,30 +56,147 @@ func TestAccCloudBuildTrigger_basic(t *testing.T) { func TestAccCloudBuildTrigger_filename(t *testing.T) { t.Parallel() + projectID := "terraform-" + acctest.RandString(10) + projectOrg := getTestOrgFromEnv(t) + projectBillingAccount := getTestBillingAccountFromEnv(t) + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, - CheckDestroy: testAccCheckCloudbuildTriggerDestroy, + CheckDestroy: testAccCheckGoogleCloudBuildTriggerVersionsDestroyed, Steps: []resource.TestStep{ { - Config: testGoogleCloudBuildTrigger_filename(), + Config: testGoogleCloudBuildTrigger_filename(projectID, projectOrg, projectBillingAccount), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleCloudFilenameConfig("google_cloudbuild_trigger.filename_build_trigger"), + ), }, { - ResourceName: "google_cloudbuild_trigger.filename_build_trigger", - ImportState: true, - ImportStateVerify: true, + Config: testGoogleCloudBuildTrigger_removed(projectID, projectOrg, projectBillingAccount), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleCloudBuildTriggerWasRemovedFromState("google_cloudbuild_trigger.filename_build_trigger"), + ), }, }, }) } -func testGoogleCloudBuildTrigger_basic() string { +func testAccGetBuildTrigger(s *terraform.State, resourceName string) (*cloudbuild.BuildTrigger, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return nil, fmt.Errorf("Resource not found: %s", resourceName) + } + + if rs.Primary.ID == "" { + return nil, fmt.Errorf("No ID is set") + } + + config := testAccProvider.Meta().(*Config) + project := rs.Primary.Attributes["project"] + + trigger, err := config.clientBuild.Projects.Triggers.Get(project, rs.Primary.ID).Do() + if err != nil { + return nil, fmt.Errorf("Trigger does not exist") + } + + return trigger, nil +} + +func testAccCheckGoogleCloudBuildTriggerExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + _, err := testAccGetBuildTrigger(s, resourceName) + + if err != nil { + return fmt.Errorf("Trigger does not exist") + } + + return nil + } +} + +func testAccCheckGoogleCloudFilenameConfig(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + trigger, err := testAccGetBuildTrigger(s, resourceName) + + if err != nil { + return fmt.Errorf("Trigger does not exist") + } + + if trigger.Filename != "cloudbuild.yaml" { + return fmt.Errorf("Config filename mismatch: %s", trigger.Filename) + } + + return nil + } +} + +func testAccCheckGoogleCloudBuildTriggerWasRemovedFromState(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + _, ok := s.RootModule().Resources[resourceName] + + if ok { + return fmt.Errorf("Resource was not removed from state: %s", resourceName) + } + + return nil + } +} + +func testAccCheckGoogleCloudBuildTriggerVersionsDestroyed(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_cloudbuild_trigger" { + continue + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + project := rs.Primary.Attributes["project"] + + _, err := config.clientBuild.Projects.Triggers.Get(project, rs.Primary.ID).Do() + if err == nil { + return fmt.Errorf("Trigger still exists") + } + + } + + return nil +} + +/* + This test runs in its own project, otherwise the test project would start to get filled + with undeletable resources +*/ +func testGoogleCloudBuildTrigger_basic(projectID, projectOrg, projectBillingAccount string) string { return fmt.Sprintf(` +resource "google_project" "acceptance" { + name = "%s" + project_id = "%s" + org_id = "%s" + billing_account = "%s" +} + +resource "google_project_services" "acceptance" { + project = "${google_project.acceptance.project_id}" + + services = [ + "cloudbuild.googleapis.com", + "containerregistry.googleapis.com", + "logging.googleapis.com", + "pubsub.googleapis.com", + "storage-api.googleapis.com", + ] +} + resource "google_cloudbuild_trigger" "build_trigger" { + project = "${google_project_services.acceptance.project}" description = "acceptance test build trigger" trigger_template { branch_name = "master" + project = "${google_project_services.acceptance.project}" repo_name = "some-repo" } build { @@ -69,27 +204,48 @@ resource "google_cloudbuild_trigger" "build_trigger" { tags = ["team-a", "service-b"] step { name = "gcr.io/cloud-builders/gsutil" - args = ["cp", "gs://mybucket/remotefile.zip", "localfile.zip"] + args = "cp gs://mybucket/remotefile.zip localfile.zip " } step { name = "gcr.io/cloud-builders/go" - args = ["build", "my_package"] + args = "build my_package" } step { name = "gcr.io/cloud-builders/docker" - args = ["build", "-t", "gcr.io/$PROJECT_ID/$REPO_NAME:$COMMIT_SHA", "-f", "Dockerfile", "."] + args = "build -t gcr.io/$PROJECT_ID/$REPO_NAME:$COMMIT_SHA -f Dockerfile ." } } } - `) + `, projectID, projectID, projectOrg, projectBillingAccount) } -func testGoogleCloudBuildTrigger_updated() string { +func testGoogleCloudBuildTrigger_updated(projectID, projectOrg, projectBillingAccount string) string { return fmt.Sprintf(` +resource "google_project" "acceptance" { + name = "%s" + project_id = "%s" + org_id = "%s" + billing_account = "%s" +} + +resource "google_project_services" "acceptance" { + project = "${google_project.acceptance.project_id}" + + services = [ + "cloudbuild.googleapis.com", + "containerregistry.googleapis.com", + "logging.googleapis.com", + "pubsub.googleapis.com", + "storage-api.googleapis.com", + ] +} + resource "google_cloudbuild_trigger" "build_trigger" { + project = "${google_project_services.acceptance.project}" description = "acceptance test build trigger updated" trigger_template { branch_name = "master-updated" + project = "${google_project_services.acceptance.project}" repo_name = "some-repo-updated" } build { @@ -97,28 +253,48 @@ resource "google_cloudbuild_trigger" "build_trigger" { tags = ["team-a", "service-b", "updated"] step { name = "gcr.io/cloud-builders/gsutil" - args = ["cp", "gs://mybucket/remotefile.zip", "localfile-updated.zip"] + args = "cp gs://mybucket/remotefile.zip localfile-updated.zip " } step { name = "gcr.io/cloud-builders/go" - args = ["build", "my_package_updated"] + args = "build my_package_updated" } step { name = "gcr.io/cloud-builders/docker" - args = ["build", "-t", "gcr.io/$PROJECT_ID/$REPO_NAME:$SHORT_SHA", "-f", "Dockerfile", "."] + args = "build -t gcr.io/$PROJECT_ID/$REPO_NAME:$SHORT_SHA -f Dockerfile ." } step { name = "gcr.io/$PROJECT_ID/$REPO_NAME:$SHORT_SHA" - args = ["test"] + args = "test" } } } - `) + `, projectID, projectID, projectOrg, projectBillingAccount) } -func testGoogleCloudBuildTrigger_filename() string { +func testGoogleCloudBuildTrigger_filename(projectID, projectOrg, projectBillingAccount string) string { return fmt.Sprintf(` +resource "google_project" "acceptance" { + name = "%s" + project_id = "%s" + org_id = "%s" + billing_account = "%s" +} + +resource "google_project_services" "acceptance" { + project = "${google_project.acceptance.project_id}" + + services = [ + "cloudbuild.googleapis.com", + "containerregistry.googleapis.com", + "logging.googleapis.com", + "pubsub.googleapis.com", + "storage-api.googleapis.com", + ] +} + resource "google_cloudbuild_trigger" "filename_build_trigger" { + project = "${google_project_services.acceptance.project}" description = "acceptance test build trigger" trigger_template { branch_name = "master" @@ -130,5 +306,28 @@ resource "google_cloudbuild_trigger" "filename_build_trigger" { } filename = "cloudbuild.yaml" } - `) + `, projectID, projectID, projectOrg, projectBillingAccount) +} + +func testGoogleCloudBuildTrigger_removed(projectID, projectOrg, projectBillingAccount string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + name = "%s" + project_id = "%s" + org_id = "%s" + billing_account = "%s" +} + +resource "google_project_services" "acceptance" { + project = "${google_project.acceptance.project_id}" + + services = [ + "cloudbuild.googleapis.com", + "containerregistry.googleapis.com", + "logging.googleapis.com", + "pubsub.googleapis.com", + "storage-api.googleapis.com", + ] +} + `, projectID, projectID, projectOrg, projectBillingAccount) } diff --git a/google-beta/resource_cloudbuild_trigger.go b/google-beta/resource_cloudbuild_trigger.go deleted file mode 100644 index bbf8cb8a44..0000000000 --- a/google-beta/resource_cloudbuild_trigger.go +++ /dev/null @@ -1,730 +0,0 @@ -// ---------------------------------------------------------------------------- -// -// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** -// -// ---------------------------------------------------------------------------- -// -// This file is automatically generated by Magic Modules and manual -// changes will be clobbered when the file is regenerated. -// -// Please read more about how to change this file in -// .github/CONTRIBUTING.md. -// -// ---------------------------------------------------------------------------- - -package google - -import ( - "fmt" - "log" - "reflect" - "time" - - "github.com/hashicorp/terraform/helper/schema" -) - -func resourceCloudbuildTrigger() *schema.Resource { - return &schema.Resource{ - Create: resourceCloudbuildTriggerCreate, - Read: resourceCloudbuildTriggerRead, - Update: resourceCloudbuildTriggerUpdate, - Delete: resourceCloudbuildTriggerDelete, - - Importer: &schema.ResourceImporter{ - State: resourceCloudbuildTriggerImport, - }, - - Timeouts: &schema.ResourceTimeout{ - Create: schema.DefaultTimeout(240 * time.Second), - Update: schema.DefaultTimeout(240 * time.Second), - Delete: schema.DefaultTimeout(240 * time.Second), - }, - - SchemaVersion: 1, - - Schema: map[string]*schema.Schema{ - "build": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "images": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "step": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "args": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "name": { - Type: schema.TypeString, - Optional: true, - }, - }, - }, - }, - "tags": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - }, - }, - ConflictsWith: []string{"filename"}, - }, - "description": { - Type: schema.TypeString, - Optional: true, - }, - "disabled": { - Type: schema.TypeString, - Optional: true, - }, - "filename": { - Type: schema.TypeString, - Optional: true, - ConflictsWith: []string{"build"}, - }, - "ignored_files": { - Type: schema.TypeList, - Optional: true, - ForceNew: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "included_files": { - Type: schema.TypeList, - Optional: true, - ForceNew: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "substitutions": { - Type: schema.TypeMap, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "trigger_template": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "branch_name": { - Type: schema.TypeString, - Optional: true, - }, - "commit_sha": { - Type: schema.TypeString, - Optional: true, - }, - "dir": { - Type: schema.TypeString, - Optional: true, - }, - "project_id": { - Type: schema.TypeString, - Computed: true, - Optional: true, - }, - "repo_name": { - Type: schema.TypeString, - Optional: true, - Default: "default", - }, - "tag_name": { - Type: schema.TypeString, - Optional: true, - }, - }, - }, - }, - "create_time": { - Type: schema.TypeString, - Computed: true, - }, - "trigger_id": { - Type: schema.TypeString, - Computed: true, - }, - "project": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - }, - }, - } -} - -func resourceCloudbuildTriggerCreate(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - - obj := make(map[string]interface{}) - descriptionProp, err := expandCloudbuildTriggerDescription(d.Get("description"), d, config) - if err != nil { - return err - } else if v, ok := d.GetOkExists("description"); !isEmptyValue(reflect.ValueOf(descriptionProp)) && (ok || !reflect.DeepEqual(v, descriptionProp)) { - obj["description"] = descriptionProp - } - disabledProp, err := expandCloudbuildTriggerDisabled(d.Get("disabled"), d, config) - if err != nil { - return err - } else if v, ok := d.GetOkExists("disabled"); !isEmptyValue(reflect.ValueOf(disabledProp)) && (ok || !reflect.DeepEqual(v, disabledProp)) { - obj["disabled"] = disabledProp - } - substitutionsProp, err := expandCloudbuildTriggerSubstitutions(d.Get("substitutions"), d, config) - if err != nil { - return err - } else if v, ok := d.GetOkExists("substitutions"); !isEmptyValue(reflect.ValueOf(substitutionsProp)) && (ok || !reflect.DeepEqual(v, substitutionsProp)) { - obj["substitutions"] = substitutionsProp - } - filenameProp, err := expandCloudbuildTriggerFilename(d.Get("filename"), d, config) - if err != nil { - return err - } else if v, ok := d.GetOkExists("filename"); !isEmptyValue(reflect.ValueOf(filenameProp)) && (ok || !reflect.DeepEqual(v, filenameProp)) { - obj["filename"] = filenameProp - } - ignoredFilesProp, err := expandCloudbuildTriggerIgnoredFiles(d.Get("ignored_files"), d, config) - if err != nil { - return err - } else if v, ok := d.GetOkExists("ignored_files"); !isEmptyValue(reflect.ValueOf(ignoredFilesProp)) && (ok || !reflect.DeepEqual(v, ignoredFilesProp)) { - obj["ignoredFiles"] = ignoredFilesProp - } - includedFilesProp, err := expandCloudbuildTriggerIncludedFiles(d.Get("included_files"), d, config) - if err != nil { - return err - } else if v, ok := d.GetOkExists("included_files"); !isEmptyValue(reflect.ValueOf(includedFilesProp)) && (ok || !reflect.DeepEqual(v, includedFilesProp)) { - obj["includedFiles"] = includedFilesProp - } - triggerTemplateProp, err := expandCloudbuildTriggerTriggerTemplate(d.Get("trigger_template"), d, config) - if err != nil { - return err - } else if v, ok := d.GetOkExists("trigger_template"); !isEmptyValue(reflect.ValueOf(triggerTemplateProp)) && (ok || !reflect.DeepEqual(v, triggerTemplateProp)) { - obj["triggerTemplate"] = triggerTemplateProp - } - buildProp, err := expandCloudbuildTriggerBuild(d.Get("build"), d, config) - if err != nil { - return err - } else if v, ok := d.GetOkExists("build"); !isEmptyValue(reflect.ValueOf(buildProp)) && (ok || !reflect.DeepEqual(v, buildProp)) { - obj["build"] = buildProp - } - - url, err := replaceVars(d, config, "https://cloudbuild.googleapis.com/v1/projects/{{project}}/triggers") - if err != nil { - return err - } - - log.Printf("[DEBUG] Creating new Trigger: %#v", obj) - res, err := sendRequestWithTimeout(config, "POST", url, obj, d.Timeout(schema.TimeoutCreate)) - if err != nil { - return fmt.Errorf("Error creating Trigger: %s", err) - } - - // Store the ID now - id, err := replaceVars(d, config, "{{project}}/{{trigger_id}}") - if err != nil { - return fmt.Errorf("Error constructing id: %s", err) - } - d.SetId(id) - - log.Printf("[DEBUG] Finished creating Trigger %q: %#v", d.Id(), res) - - // `name` is autogenerated from the api so needs to be set post-create - triggerId, ok := res["id"] - if !ok { - return fmt.Errorf("Create response didn't contain id. Create may not have succeeded.") - } - d.Set("trigger_id", triggerId.(string)) - - // Store the ID now. We tried to set it before and it failed because - // trigger_id didn't exist yet. - id, err = replaceVars(d, config, "{{project}}/{{trigger_id}}") - if err != nil { - return fmt.Errorf("Error constructing id: %s", err) - } - d.SetId(id) - - return resourceCloudbuildTriggerRead(d, meta) -} - -func resourceCloudbuildTriggerRead(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - - url, err := replaceVars(d, config, "https://cloudbuild.googleapis.com/v1/projects/{{project}}/triggers/{{trigger_id}}") - if err != nil { - return err - } - - res, err := sendRequest(config, "GET", url, nil) - if err != nil { - return handleNotFoundError(err, d, fmt.Sprintf("CloudbuildTrigger %q", d.Id())) - } - - project, err := getProject(d, config) - if err != nil { - return err - } - if err := d.Set("project", project); err != nil { - return fmt.Errorf("Error reading Trigger: %s", err) - } - - if err := d.Set("trigger_id", flattenCloudbuildTriggerTrigger_id(res["id"], d)); err != nil { - return fmt.Errorf("Error reading Trigger: %s", err) - } - if err := d.Set("description", flattenCloudbuildTriggerDescription(res["description"], d)); err != nil { - return fmt.Errorf("Error reading Trigger: %s", err) - } - if err := d.Set("disabled", flattenCloudbuildTriggerDisabled(res["disabled"], d)); err != nil { - return fmt.Errorf("Error reading Trigger: %s", err) - } - if err := d.Set("create_time", flattenCloudbuildTriggerCreateTime(res["createTime"], d)); err != nil { - return fmt.Errorf("Error reading Trigger: %s", err) - } - if err := d.Set("substitutions", flattenCloudbuildTriggerSubstitutions(res["substitutions"], d)); err != nil { - return fmt.Errorf("Error reading Trigger: %s", err) - } - if err := d.Set("filename", flattenCloudbuildTriggerFilename(res["filename"], d)); err != nil { - return fmt.Errorf("Error reading Trigger: %s", err) - } - if err := d.Set("ignored_files", flattenCloudbuildTriggerIgnoredFiles(res["ignoredFiles"], d)); err != nil { - return fmt.Errorf("Error reading Trigger: %s", err) - } - if err := d.Set("included_files", flattenCloudbuildTriggerIncludedFiles(res["includedFiles"], d)); err != nil { - return fmt.Errorf("Error reading Trigger: %s", err) - } - if err := d.Set("trigger_template", flattenCloudbuildTriggerTriggerTemplate(res["triggerTemplate"], d)); err != nil { - return fmt.Errorf("Error reading Trigger: %s", err) - } - if err := d.Set("build", flattenCloudbuildTriggerBuild(res["build"], d)); err != nil { - return fmt.Errorf("Error reading Trigger: %s", err) - } - - return nil -} - -func resourceCloudbuildTriggerUpdate(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - - obj := make(map[string]interface{}) - descriptionProp, err := expandCloudbuildTriggerDescription(d.Get("description"), d, config) - if err != nil { - return err - } else if v, ok := d.GetOkExists("description"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, descriptionProp)) { - obj["description"] = descriptionProp - } - disabledProp, err := expandCloudbuildTriggerDisabled(d.Get("disabled"), d, config) - if err != nil { - return err - } else if v, ok := d.GetOkExists("disabled"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, disabledProp)) { - obj["disabled"] = disabledProp - } - substitutionsProp, err := expandCloudbuildTriggerSubstitutions(d.Get("substitutions"), d, config) - if err != nil { - return err - } else if v, ok := d.GetOkExists("substitutions"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, substitutionsProp)) { - obj["substitutions"] = substitutionsProp - } - filenameProp, err := expandCloudbuildTriggerFilename(d.Get("filename"), d, config) - if err != nil { - return err - } else if v, ok := d.GetOkExists("filename"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, filenameProp)) { - obj["filename"] = filenameProp - } - triggerTemplateProp, err := expandCloudbuildTriggerTriggerTemplate(d.Get("trigger_template"), d, config) - if err != nil { - return err - } else if v, ok := d.GetOkExists("trigger_template"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, triggerTemplateProp)) { - obj["triggerTemplate"] = triggerTemplateProp - } - buildProp, err := expandCloudbuildTriggerBuild(d.Get("build"), d, config) - if err != nil { - return err - } else if v, ok := d.GetOkExists("build"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, buildProp)) { - obj["build"] = buildProp - } - - url, err := replaceVars(d, config, "https://cloudbuild.googleapis.com/v1/projects/{{project}}/triggers/{{trigger_id}}") - if err != nil { - return err - } - - log.Printf("[DEBUG] Updating Trigger %q: %#v", d.Id(), obj) - obj["id"] = d.Get("trigger_id") - _, err = sendRequestWithTimeout(config, "PATCH", url, obj, d.Timeout(schema.TimeoutUpdate)) - - if err != nil { - return fmt.Errorf("Error updating Trigger %q: %s", d.Id(), err) - } - - return resourceCloudbuildTriggerRead(d, meta) -} - -func resourceCloudbuildTriggerDelete(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - - url, err := replaceVars(d, config, "https://cloudbuild.googleapis.com/v1/projects/{{project}}/triggers/{{trigger_id}}") - if err != nil { - return err - } - - var obj map[string]interface{} - log.Printf("[DEBUG] Deleting Trigger %q", d.Id()) - res, err := sendRequestWithTimeout(config, "DELETE", url, obj, d.Timeout(schema.TimeoutDelete)) - if err != nil { - return handleNotFoundError(err, d, "Trigger") - } - - log.Printf("[DEBUG] Finished deleting Trigger %q: %#v", d.Id(), res) - return nil -} - -func resourceCloudbuildTriggerImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - config := meta.(*Config) - if err := parseImportId([]string{"projects/(?P[^/]+)/triggers/(?P[^/]+)", "(?P[^/]+)/(?P[^/]+)", "(?P[^/]+)"}, d, config); err != nil { - return nil, err - } - - // Replace import id for the resource id - id, err := replaceVars(d, config, "{{project}}/{{trigger_id}}") - if err != nil { - return nil, fmt.Errorf("Error constructing id: %s", err) - } - d.SetId(id) - - return []*schema.ResourceData{d}, nil -} - -func flattenCloudbuildTriggerTrigger_id(v interface{}, d *schema.ResourceData) interface{} { - return v -} - -func flattenCloudbuildTriggerDescription(v interface{}, d *schema.ResourceData) interface{} { - return v -} - -func flattenCloudbuildTriggerDisabled(v interface{}, d *schema.ResourceData) interface{} { - return v -} - -func flattenCloudbuildTriggerCreateTime(v interface{}, d *schema.ResourceData) interface{} { - return v -} - -func flattenCloudbuildTriggerSubstitutions(v interface{}, d *schema.ResourceData) interface{} { - return v -} - -func flattenCloudbuildTriggerFilename(v interface{}, d *schema.ResourceData) interface{} { - return v -} - -func flattenCloudbuildTriggerIgnoredFiles(v interface{}, d *schema.ResourceData) interface{} { - return v -} - -func flattenCloudbuildTriggerIncludedFiles(v interface{}, d *schema.ResourceData) interface{} { - return v -} - -func flattenCloudbuildTriggerTriggerTemplate(v interface{}, d *schema.ResourceData) interface{} { - if v == nil { - return nil - } - original := v.(map[string]interface{}) - if len(original) == 0 { - return nil - } - transformed := make(map[string]interface{}) - transformed["project_id"] = - flattenCloudbuildTriggerTriggerTemplateProjectId(original["projectId"], d) - transformed["repo_name"] = - flattenCloudbuildTriggerTriggerTemplateRepoName(original["repoName"], d) - transformed["dir"] = - flattenCloudbuildTriggerTriggerTemplateDir(original["dir"], d) - transformed["branch_name"] = - flattenCloudbuildTriggerTriggerTemplateBranchName(original["branchName"], d) - transformed["tag_name"] = - flattenCloudbuildTriggerTriggerTemplateTagName(original["tagName"], d) - transformed["commit_sha"] = - flattenCloudbuildTriggerTriggerTemplateCommitSha(original["commitSha"], d) - return []interface{}{transformed} -} -func flattenCloudbuildTriggerTriggerTemplateProjectId(v interface{}, d *schema.ResourceData) interface{} { - return v -} - -func flattenCloudbuildTriggerTriggerTemplateRepoName(v interface{}, d *schema.ResourceData) interface{} { - return v -} - -func flattenCloudbuildTriggerTriggerTemplateDir(v interface{}, d *schema.ResourceData) interface{} { - return v -} - -func flattenCloudbuildTriggerTriggerTemplateBranchName(v interface{}, d *schema.ResourceData) interface{} { - return v -} - -func flattenCloudbuildTriggerTriggerTemplateTagName(v interface{}, d *schema.ResourceData) interface{} { - return v -} - -func flattenCloudbuildTriggerTriggerTemplateCommitSha(v interface{}, d *schema.ResourceData) interface{} { - return v -} - -func flattenCloudbuildTriggerBuild(v interface{}, d *schema.ResourceData) interface{} { - if v == nil { - return nil - } - original := v.(map[string]interface{}) - if len(original) == 0 { - return nil - } - transformed := make(map[string]interface{}) - transformed["tags"] = - flattenCloudbuildTriggerBuildTags(original["tags"], d) - transformed["images"] = - flattenCloudbuildTriggerBuildImages(original["images"], d) - transformed["step"] = - flattenCloudbuildTriggerBuildStep(original["steps"], d) - return []interface{}{transformed} -} -func flattenCloudbuildTriggerBuildTags(v interface{}, d *schema.ResourceData) interface{} { - return v -} - -func flattenCloudbuildTriggerBuildImages(v interface{}, d *schema.ResourceData) interface{} { - return v -} - -func flattenCloudbuildTriggerBuildStep(v interface{}, d *schema.ResourceData) interface{} { - if v == nil { - return v - } - l := v.([]interface{}) - transformed := make([]interface{}, 0, len(l)) - for _, raw := range l { - original := raw.(map[string]interface{}) - if len(original) < 1 { - // Do not include empty json objects coming back from the api - continue - } - transformed = append(transformed, map[string]interface{}{ - "name": flattenCloudbuildTriggerBuildStepName(original["name"], d), - "args": flattenCloudbuildTriggerBuildStepArgs(original["args"], d), - }) - } - return transformed -} -func flattenCloudbuildTriggerBuildStepName(v interface{}, d *schema.ResourceData) interface{} { - return v -} - -func flattenCloudbuildTriggerBuildStepArgs(v interface{}, d *schema.ResourceData) interface{} { - return v -} - -func expandCloudbuildTriggerDescription(v interface{}, d *schema.ResourceData, config *Config) (interface{}, error) { - return v, nil -} - -func expandCloudbuildTriggerDisabled(v interface{}, d *schema.ResourceData, config *Config) (interface{}, error) { - return v, nil -} - -func expandCloudbuildTriggerSubstitutions(v interface{}, d *schema.ResourceData, config *Config) (map[string]string, error) { - if v == nil { - return map[string]string{}, nil - } - m := make(map[string]string) - for k, val := range v.(map[string]interface{}) { - m[k] = val.(string) - } - return m, nil -} - -func expandCloudbuildTriggerFilename(v interface{}, d *schema.ResourceData, config *Config) (interface{}, error) { - return v, nil -} - -func expandCloudbuildTriggerIgnoredFiles(v interface{}, d *schema.ResourceData, config *Config) (interface{}, error) { - return v, nil -} - -func expandCloudbuildTriggerIncludedFiles(v interface{}, d *schema.ResourceData, config *Config) (interface{}, error) { - return v, nil -} - -func expandCloudbuildTriggerTriggerTemplate(v interface{}, d *schema.ResourceData, 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{}) - - transformedProjectId, err := expandCloudbuildTriggerTriggerTemplateProjectId(original["project_id"], d, config) - if err != nil { - return nil, err - } else if val := reflect.ValueOf(transformedProjectId); val.IsValid() && !isEmptyValue(val) { - transformed["projectId"] = transformedProjectId - } - - transformedRepoName, err := expandCloudbuildTriggerTriggerTemplateRepoName(original["repo_name"], d, config) - if err != nil { - return nil, err - } else if val := reflect.ValueOf(transformedRepoName); val.IsValid() && !isEmptyValue(val) { - transformed["repoName"] = transformedRepoName - } - - transformedDir, err := expandCloudbuildTriggerTriggerTemplateDir(original["dir"], d, config) - if err != nil { - return nil, err - } else if val := reflect.ValueOf(transformedDir); val.IsValid() && !isEmptyValue(val) { - transformed["dir"] = transformedDir - } - - transformedBranchName, err := expandCloudbuildTriggerTriggerTemplateBranchName(original["branch_name"], d, config) - if err != nil { - return nil, err - } else if val := reflect.ValueOf(transformedBranchName); val.IsValid() && !isEmptyValue(val) { - transformed["branchName"] = transformedBranchName - } - - transformedTagName, err := expandCloudbuildTriggerTriggerTemplateTagName(original["tag_name"], d, config) - if err != nil { - return nil, err - } else if val := reflect.ValueOf(transformedTagName); val.IsValid() && !isEmptyValue(val) { - transformed["tagName"] = transformedTagName - } - - transformedCommitSha, err := expandCloudbuildTriggerTriggerTemplateCommitSha(original["commit_sha"], d, config) - if err != nil { - return nil, err - } else if val := reflect.ValueOf(transformedCommitSha); val.IsValid() && !isEmptyValue(val) { - transformed["commitSha"] = transformedCommitSha - } - - return transformed, nil -} - -func expandCloudbuildTriggerTriggerTemplateProjectId(v interface{}, d *schema.ResourceData, config *Config) (interface{}, error) { - return v, nil -} - -func expandCloudbuildTriggerTriggerTemplateRepoName(v interface{}, d *schema.ResourceData, config *Config) (interface{}, error) { - return v, nil -} - -func expandCloudbuildTriggerTriggerTemplateDir(v interface{}, d *schema.ResourceData, config *Config) (interface{}, error) { - return v, nil -} - -func expandCloudbuildTriggerTriggerTemplateBranchName(v interface{}, d *schema.ResourceData, config *Config) (interface{}, error) { - return v, nil -} - -func expandCloudbuildTriggerTriggerTemplateTagName(v interface{}, d *schema.ResourceData, config *Config) (interface{}, error) { - return v, nil -} - -func expandCloudbuildTriggerTriggerTemplateCommitSha(v interface{}, d *schema.ResourceData, config *Config) (interface{}, error) { - return v, nil -} - -func expandCloudbuildTriggerBuild(v interface{}, d *schema.ResourceData, 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{}) - - transformedTags, err := expandCloudbuildTriggerBuildTags(original["tags"], d, config) - if err != nil { - return nil, err - } else if val := reflect.ValueOf(transformedTags); val.IsValid() && !isEmptyValue(val) { - transformed["tags"] = transformedTags - } - - transformedImages, err := expandCloudbuildTriggerBuildImages(original["images"], d, config) - if err != nil { - return nil, err - } else if val := reflect.ValueOf(transformedImages); val.IsValid() && !isEmptyValue(val) { - transformed["images"] = transformedImages - } - - transformedStep, err := expandCloudbuildTriggerBuildStep(original["step"], d, config) - if err != nil { - return nil, err - } else if val := reflect.ValueOf(transformedStep); val.IsValid() && !isEmptyValue(val) { - transformed["steps"] = transformedStep - } - - return transformed, nil -} - -func expandCloudbuildTriggerBuildTags(v interface{}, d *schema.ResourceData, config *Config) (interface{}, error) { - return v, nil -} - -func expandCloudbuildTriggerBuildImages(v interface{}, d *schema.ResourceData, config *Config) (interface{}, error) { - return v, nil -} - -func expandCloudbuildTriggerBuildStep(v interface{}, d *schema.ResourceData, config *Config) (interface{}, error) { - l := v.([]interface{}) - req := make([]interface{}, 0, len(l)) - for _, raw := range l { - if raw == nil { - continue - } - original := raw.(map[string]interface{}) - transformed := make(map[string]interface{}) - - transformedName, err := expandCloudbuildTriggerBuildStepName(original["name"], d, config) - if err != nil { - return nil, err - } else if val := reflect.ValueOf(transformedName); val.IsValid() && !isEmptyValue(val) { - transformed["name"] = transformedName - } - - transformedArgs, err := expandCloudbuildTriggerBuildStepArgs(original["args"], d, config) - if err != nil { - return nil, err - } else if val := reflect.ValueOf(transformedArgs); val.IsValid() && !isEmptyValue(val) { - transformed["args"] = transformedArgs - } - - req = append(req, transformed) - } - return req, nil -} - -func expandCloudbuildTriggerBuildStepName(v interface{}, d *schema.ResourceData, config *Config) (interface{}, error) { - return v, nil -} - -func expandCloudbuildTriggerBuildStepArgs(v interface{}, d *schema.ResourceData, config *Config) (interface{}, error) { - return v, nil -} diff --git a/google-beta/resource_cloudbuild_trigger_generated_test.go b/google-beta/resource_cloudbuild_trigger_generated_test.go deleted file mode 100644 index e6207b370f..0000000000 --- a/google-beta/resource_cloudbuild_trigger_generated_test.go +++ /dev/null @@ -1,92 +0,0 @@ -// ---------------------------------------------------------------------------- -// -// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** -// -// ---------------------------------------------------------------------------- -// -// This file is automatically generated by Magic Modules and manual -// changes will be clobbered when the file is regenerated. -// -// Please read more about how to change this file in -// .github/CONTRIBUTING.md. -// -// ---------------------------------------------------------------------------- - -package google - -import ( - "fmt" - "strings" - "testing" - - "github.com/hashicorp/terraform/helper/acctest" - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" -) - -func TestAccCloudbuildTrigger_cloudbuildTriggerFilenameExample(t *testing.T) { - t.Parallel() - - context := map[string]interface{}{ - "random_suffix": acctest.RandString(10), - } - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckCloudbuildTriggerDestroy, - Steps: []resource.TestStep{ - { - Config: testAccCloudbuildTrigger_cloudbuildTriggerFilenameExample(context), - }, - { - ResourceName: "google_cloudbuild_trigger.filename-trigger", - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -func testAccCloudbuildTrigger_cloudbuildTriggerFilenameExample(context map[string]interface{}) string { - return Nprintf(` -resource "google_cloudbuild_trigger" "filename-trigger" { - trigger_template { - branch_name = "master" - repo_name = "my-repo" - } - - substitutions = { - _FOO = "bar" - _BAZ = "qux" - } - - filename = "cloudbuild.yaml" -} -`, context) -} - -func testAccCheckCloudbuildTriggerDestroy(s *terraform.State) error { - for name, rs := range s.RootModule().Resources { - if rs.Type != "google_cloudbuild_trigger" { - continue - } - if strings.HasPrefix(name, "data.") { - continue - } - - config := testAccProvider.Meta().(*Config) - - url, err := replaceVarsForTest(rs, "https://cloudbuild.googleapis.com/v1/projects/{{project}}/triggers/{{trigger_id}}") - if err != nil { - return err - } - - _, err = sendRequest(config, "GET", url, nil) - if err == nil { - return fmt.Errorf("CloudbuildTrigger still exists at %s", url) - } - } - - return nil -} diff --git a/google-beta/resource_compute_route.go b/google-beta/resource_compute_route.go index 1afa1dbe57..708c758e59 100644 --- a/google-beta/resource_compute_route.go +++ b/google-beta/resource_compute_route.go @@ -392,10 +392,7 @@ func flattenComputeRouteNextHopGateway(v interface{}, d *schema.ResourceData) in } func flattenComputeRouteNextHopInstance(v interface{}, d *schema.ResourceData) interface{} { - if v == nil { - return v - } - return ConvertSelfLinkToV1(v.(string)) + return v } func flattenComputeRouteNextHopIp(v interface{}, d *schema.ResourceData) interface{} { @@ -454,11 +451,18 @@ func expandComputeRouteNextHopGateway(v interface{}, d *schema.ResourceData, con } func expandComputeRouteNextHopInstance(v interface{}, d *schema.ResourceData, config *Config) (interface{}, error) { - f, err := parseZonalFieldValue("instances", v.(string), "project", "zone", d, config, true) + if v == "" { + return v, nil + } + val, err := parseZonalFieldValue("instances", v.(string), "project", "next_hop_instance_zone", d, config, true) if err != nil { - return nil, fmt.Errorf("Invalid value for next_hop_instance: %s", err) + return nil, err } - return f.RelativeLink(), nil + nextInstance, err := config.clientCompute.Instances.Get(val.Project, val.Zone, val.Name).Do() + if err != nil { + return nil, err + } + return nextInstance.SelfLink, nil } func expandComputeRouteNextHopIp(v interface{}, d *schema.ResourceData, config *Config) (interface{}, error) { diff --git a/google-beta/resource_google_project_iam_policy_test.go b/google-beta/resource_google_project_iam_policy_test.go index 5d57240657..c7180c6eca 100644 --- a/google-beta/resource_google_project_iam_policy_test.go +++ b/google-beta/resource_google_project_iam_policy_test.go @@ -169,6 +169,62 @@ func testAccCheckGoogleProjectIamPolicyExists(projectRes, policyRes, pid string) } } +func TestIamOverwriteBinding(t *testing.T) { + table := []struct { + input []*cloudresourcemanager.Binding + override cloudresourcemanager.Binding + expect []cloudresourcemanager.Binding + }{ + { + input: []*cloudresourcemanager.Binding{ + { + Role: "role-1", + Members: []string{"member-1", "member-2"}, + }, + }, + override: cloudresourcemanager.Binding{ + Role: "role-1", + Members: []string{"new-member"}, + }, + expect: []cloudresourcemanager.Binding{ + { + Role: "role-1", + Members: []string{"new-member"}, + }, + }, + }, + { + input: []*cloudresourcemanager.Binding{ + { + Role: "role-1", + Members: []string{"member-1", "member-2"}, + }, + }, + override: cloudresourcemanager.Binding{ + Role: "role-2", + Members: []string{"member-3"}, + }, + expect: []cloudresourcemanager.Binding{ + { + Role: "role-1", + Members: []string{"member-1", "member-2"}, + }, + { + Role: "role-2", + Members: []string{"member-3"}, + }, + }, + }, + } + + for _, test := range table { + got := overwriteBinding(test.input, &test.override) + if !reflect.DeepEqual(derefBindings(got), test.expect) { + t.Errorf("OverwriteIamBinding failed.\nGot %+v\nWant %+v", derefBindings(got), test.expect) + } + } +} + func TestIamMergeBindings(t *testing.T) { table := []struct { input []*cloudresourcemanager.Binding @@ -177,95 +233,61 @@ func TestIamMergeBindings(t *testing.T) { { input: []*cloudresourcemanager.Binding{ { - Role: "role-1", - Members: []string{ - "member-1", - "member-2", - }, + Role: "role-1", + Members: []string{"member-1", "member-2"}, }, { - Role: "role-1", - Members: []string{ - "member-3", - }, + Role: "role-1", + Members: []string{"member-3"}, }, }, expect: []cloudresourcemanager.Binding{ { - Role: "role-1", - Members: []string{ - "member-1", - "member-2", - "member-3", - }, + Role: "role-1", + Members: []string{"member-1", "member-2", "member-3"}, }, }, }, { input: []*cloudresourcemanager.Binding{ { - Role: "role-1", - Members: []string{ - "member-3", - "member-4", - }, + Role: "role-1", + Members: []string{"member-3", "member-4"}, }, { - Role: "role-1", - Members: []string{ - "member-2", - "member-1", - }, + Role: "role-1", + Members: []string{"member-2", "member-1"}, }, { - Role: "role-2", - Members: []string{ - "member-1", - }, + Role: "role-2", + Members: []string{"member-1"}, }, { - Role: "role-1", - Members: []string{ - "member-5", - }, + Role: "role-1", + Members: []string{"member-5"}, }, { - Role: "role-3", - Members: []string{ - "member-1", - }, + Role: "role-3", + Members: []string{"member-1"}, }, { - Role: "role-2", - Members: []string{ - "member-2", - }, + Role: "role-2", + Members: []string{"member-2"}, }, {Role: "empty-role", Members: []string{}}, }, expect: []cloudresourcemanager.Binding{ { - Role: "role-1", - Members: []string{ - "member-1", - "member-2", - "member-3", - "member-4", - "member-5", - }, + Role: "role-1", + Members: []string{"member-1", "member-2", "member-3", "member-4", "member-5"}, }, { - Role: "role-2", - Members: []string{ - "member-1", - "member-2", - }, + Role: "role-2", + Members: []string{"member-1", "member-2"}, }, { - Role: "role-3", - Members: []string{ - "member-1", - }, + Role: "role-3", + Members: []string{"member-1"}, }, }, }, @@ -416,7 +438,7 @@ data "google_iam_policy" "expanded" { "user:paddy@carvers.co", ] } - + binding { role = "roles/viewer" members = [ diff --git a/google-beta/resource_iam_binding.go b/google-beta/resource_iam_binding.go index 6be5765198..5a8d072adf 100644 --- a/google-beta/resource_iam_binding.go +++ b/google-beta/resource_iam_binding.go @@ -31,9 +31,9 @@ var iamBindingSchema = map[string]*schema.Schema{ func ResourceIamBinding(parentSpecificSchema map[string]*schema.Schema, newUpdaterFunc newResourceIamUpdaterFunc) *schema.Resource { return &schema.Resource{ - Create: resourceIamBindingCreate(newUpdaterFunc), + Create: resourceIamBindingCreateUpdate(newUpdaterFunc), Read: resourceIamBindingRead(newUpdaterFunc), - Update: resourceIamBindingUpdate(newUpdaterFunc), + Update: resourceIamBindingCreateUpdate(newUpdaterFunc), Delete: resourceIamBindingDelete(newUpdaterFunc), Schema: mergeSchemas(iamBindingSchema, parentSpecificSchema), } @@ -47,7 +47,7 @@ func ResourceIamBindingWithImport(parentSpecificSchema map[string]*schema.Schema return r } -func resourceIamBindingCreate(newUpdaterFunc newResourceIamUpdaterFunc) schema.CreateFunc { +func resourceIamBindingCreateUpdate(newUpdaterFunc newResourceIamUpdaterFunc) func(*schema.ResourceData, interface{}) error { return func(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) updater, err := newUpdaterFunc(d, config) @@ -57,11 +57,7 @@ func resourceIamBindingCreate(newUpdaterFunc newResourceIamUpdaterFunc) schema.C p := getResourceIamBinding(d) err = iamPolicyReadModifyWrite(updater, func(ep *cloudresourcemanager.Policy) error { - // Creating a binding does not remove existing members if they are not in the provided members list. - // This prevents removing existing permission without the user's knowledge. - // Instead, a diff is shown in that case after creation. Subsequent calls to update will remove any - // existing members not present in the provided list. - ep.Bindings = mergeBindings(append(ep.Bindings, p)) + ep.Bindings = overwriteBinding(ep.Bindings, p) return nil }) if err != nil { @@ -151,38 +147,6 @@ func iamBindingImport(resourceIdParser resourceIdParserFunc) schema.StateFunc { } } -func resourceIamBindingUpdate(newUpdaterFunc newResourceIamUpdaterFunc) schema.UpdateFunc { - return func(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - updater, err := newUpdaterFunc(d, config) - if err != nil { - return err - } - - binding := getResourceIamBinding(d) - err = iamPolicyReadModifyWrite(updater, func(p *cloudresourcemanager.Policy) error { - var found bool - for pos, b := range p.Bindings { - if b.Role != binding.Role { - continue - } - found = true - p.Bindings[pos] = binding - break - } - if !found { - p.Bindings = append(p.Bindings, binding) - } - return nil - }) - if err != nil { - return err - } - - return resourceIamBindingRead(newUpdaterFunc)(d, meta) - } -} - func resourceIamBindingDelete(newUpdaterFunc newResourceIamUpdaterFunc) schema.DeleteFunc { return func(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) diff --git a/google-beta/utils.go b/google-beta/utils.go index 305ee7ef86..3c1bde6573 100644 --- a/google-beta/utils.go +++ b/google-beta/utils.go @@ -212,6 +212,19 @@ func expandEnvironmentVariables(d *schema.ResourceData) map[string]string { return expandStringMap(d, "environment_variables") } +// expandStringSlice pulls the value of key out of schema.ResourceData as a []string +func expandStringSlice(d *schema.ResourceData, key string) []string { + var strings []string + + if interfaceStrings, ok := d.GetOk(key); ok { + for _, str := range interfaceStrings.([]interface{}) { + strings = append(strings, str.(string)) + } + } + + return strings +} + // expandStringMap pulls the value of key out of a schema.ResourceData as a map[string]string. func expandStringMap(d *schema.ResourceData, key string) map[string]string { v, ok := d.GetOk(key) diff --git a/website/docs/d/google_iam_policy.html.markdown b/website/docs/d/google_iam_policy.html.markdown index f3a454ef81..a3f14c1c6b 100644 --- a/website/docs/d/google_iam_policy.html.markdown +++ b/website/docs/d/google_iam_policy.html.markdown @@ -26,10 +26,10 @@ data "google_iam_policy" "admin" { role = "roles/storage.objectViewer" members = [ - "user:jane@example.com", + "user:alice@gmail.com", ] } - + audit_config { service = "cloudkms.googleapis.com" audit_log_configs = [ @@ -73,11 +73,11 @@ each accept the following arguments: See the [IAM Roles](https://cloud.google.com/compute/docs/access/iam) documentation for a complete list of roles. Note that custom roles must be of the format `[projects|organizations]/{parent-name}/roles/{role-name}`. -* `members` (Required) - An array of identites that will be granted the privilege in the `role`. +* `members` (Required) - An array of identites that will be granted the privilege in the `role`. For more details on format and restrictions see https://cloud.google.com/billing/reference/rest/v1/Policy#Binding Each entry can have one of the following values: * **allUsers**: A special identifier that represents anyone who is on the internet; with or without a Google account. It **can't** be used with the `google_project` resource. * **allAuthenticatedUsers**: A special identifier that represents anyone who is authenticated with a Google account or a service account. It **can't** be used with the `google_project` resource. - * **user:{emailid}**: An email address that represents a specific Google account. For example, alice@gmail.com or joe@example.com. + * **user:{emailid}**: An email address that represents a specific Google account. For example, alice@gmail.com. * **serviceAccount:{emailid}**: An email address that represents a service account. For example, my-other-app@appspot.gserviceaccount.com. * **group:{emailid}**: An email address that represents a Google group. For example, admins@example.com. * **domain:{domain}**: A G Suite domain (primary, instead of alias) name that represents all the users of that domain. For example, google.com or example.com. diff --git a/website/docs/r/cloudbuild_trigger.html.markdown b/website/docs/r/cloudbuild_trigger.html.markdown index 6827812c26..d5694d5bfc 100644 --- a/website/docs/r/cloudbuild_trigger.html.markdown +++ b/website/docs/r/cloudbuild_trigger.html.markdown @@ -1,223 +1,157 @@ --- -# ---------------------------------------------------------------------------- -# -# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** -# -# ---------------------------------------------------------------------------- -# -# This file is automatically generated by Magic Modules and manual -# changes will be clobbered when the file is regenerated. -# -# Please read more about how to change this file in -# .github/CONTRIBUTING.md. -# -# ---------------------------------------------------------------------------- layout: "google" page_title: "Google: google_cloudbuild_trigger" sidebar_current: "docs-google-cloudbuild-trigger" description: |- - Configuration for an automated build in response to source repository changes. + Creates a new build trigger within GCR. --- # google\_cloudbuild\_trigger -Configuration for an automated build in response to source repository changes. - - -To get more information about Trigger, see: - -* [API documentation](https://cloud.google.com/cloud-build/docs/api/reference/rest/) -* How-to Guides - * [Automating builds using build triggers](https://cloud.google.com/cloud-build/docs/running-builds/automate-builds) - - -## Example Usage - Cloudbuild Trigger Filename +Creates a new build trigger within GCR. For more information, see +[the official documentation](https://cloud.google.com/container-builder/docs/running-builds/automate-builds) +and +[API](https://godoc.org/google.golang.org/api/cloudbuild/v1#BuildTrigger). +## Example Usage ```hcl -resource "google_cloudbuild_trigger" "filename-trigger" { +resource "google_cloudbuild_trigger" "build_trigger" { + project = "my-project" trigger_template { branch_name = "master" - repo_name = "my-repo" + project = "my-project" + repo_name = "some-repo" } - - substitutions = { - _FOO = "bar" - _BAZ = "qux" + build { + images = ["gcr.io/$PROJECT_ID/$REPO_NAME:$COMMIT_SHA"] + step { + name = "gcr.io/cloud-builders/docker" + args = "build -t gcr.io/$PROJECT_ID/$REPO_NAME:$COMMIT_SHA -f Dockerfile ." + } } - - filename = "cloudbuild.yaml" } ``` -## Argument Reference - -The following arguments are supported: - - - -- - - - - -* `description` - - (Optional) - Human-readable description of the trigger. - -* `disabled` - - (Optional) - Whether the trigger is disabled or not. If true, the trigger will never result in a build. - -* `substitutions` - - (Optional) - Substitutions data for Build resource. - -* `filename` - - (Optional) - Path, from the source root, to a file whose contents is used for the template. - -* `ignored_files` - - (Optional) - ignoredFiles and includedFiles are file glob matches using http://godoc/pkg/path/filepath#Match - extended with support for "**". - If ignoredFiles and changed files are both empty, then they are not - used to determine whether or not to trigger a build. - If ignoredFiles is not empty, then we ignore any files that match any - of the ignored_file globs. If the change has no files that are outside - of the ignoredFiles globs, then we do not trigger a build. - -* `included_files` - - (Optional) - ignoredFiles and includedFiles are file glob matches using http://godoc/pkg/path/filepath#Match - extended with support for "**". - If any of the files altered in the commit pass the ignoredFiles filter - and includedFiles is empty, then as far as this filter is concerned, we - should trigger the build. - If any of the files altered in the commit pass the ignoredFiles filter - and includedFiles is not empty, then we make sure that at least one of - those files matches a includedFiles glob. If not, then we do not trigger - a build. - -* `trigger_template` - - (Optional) - Template describing the types of source changes to trigger a build. - 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. Structure is documented below. - -* `build` - - (Optional) - Contents of the build template. Structure is documented below. -* `project` - (Optional) The ID of the project in which the resource belongs. - If it is not provided, the provider project is used. +OR +```hcl +resource "google_cloudbuild_trigger" "build_trigger" { + project = "my-project" + trigger_template { + branch_name = "master" + project = "my-project" + repo_name = "some-repo" + } + filename = "cloudbuild.yaml" +} +``` -The `trigger_template` block supports: - -* `project_id` - - (Optional) - ID of the project that owns the Cloud Source Repository. If - omitted, the project ID requesting the build is assumed. -* `repo_name` - - (Optional) - Name of the Cloud Source Repository. If omitted, the name "default" is assumed. +## Argument Reference -* `dir` - - (Optional) - Directory, relative to the source root, in which to run the build. - This must be a relative path. If a step's dir is specified and - is an absolute path, this value is ignored for that step's - execution. +(Argument descriptions sourced from https://godoc.org/google.golang.org/api/cloudbuild/v1#BuildTrigger) -* `branch_name` - - (Optional) - Name of the branch to build. +The following arguments are supported: -* `tag_name` - - (Optional) - Name of the tag to build. +* `build` - (Optional) A build resource in the Container Builder API. +Structure is documented below. At a high +level, a `build` describes where to find source code, how to build it (for +example, the builder image to run on the source), and where to store +the built artifacts. Fields can include the following variables, which +will be expanded when the build is created: + * `$PROJECT_ID`: the project ID of the build. + * `$BUILD_ID`: the autogenerated ID of the build. + * `$REPO_NAME`: the source repository name specified by RepoSource. + * `$BRANCH_NAME`: the branch name specified by RepoSource. + * `$TAG_NAME`: the tag name specified by RepoSource. + * `$REVISION_ID` or `$COMMIT_SHA`: the commit SHA specified by RepoSource + or resolved from the specified branch or tag. + * `$SHORT_SHA`: first 7 characters of `$REVISION_ID` or `$COMMIT_SHA`. + +* `description` - (Optional) A brief description of this resource. + +* `ignored_files` - (Optional) `ignored_files` and `included_files` are file glob matches using http://godoc/pkg/path/filepath#Match extended with support for "\*\*". If `ignored_files` and changed files are both empty, then they are not used to determine whether or not to trigger a build. If `ignored_files` is not empty, then we ignore any files that match any of the ignored_file globs. If the change has no files that are outside of the `ignored_files` globs, then + we do not trigger a build. + +* `included_files` - (Optional) If any of the files altered in the commit pass the `ignored_files` filter and `included_files` is empty, then as far as this filter is concerned, we should trigger the build. If any of the files altered in the commit pass the `ignored_files` filter and `included_files` is not empty, then we make sure that at least one of those files matches a `included_files` glob. If not, then we do not trigger a build. + +* `filename` - (Optional) Specify the path to a Cloud Build configuration file +in the Git repo. This is mutually exclusive with `build`. This is typically +`cloudbuild.yaml` however it can be specified by the user. + +* `project` - (Optional) The ID of the project that the trigger will be created in. +Defaults to the provider project configuration. + +* `substitutions`: (Optional) User-defined substitutions. +User-defined substitutions must conform to the following rules: + * Substitutions must begin with an underscore (`_`) and use only + uppercase-letters and numbers (respecting the regular expression + `_[A-Z0-9_]+`). This prevents conflicts with built-in substitutions. + * Unmatched keys in the template will cause an error (for example, if a build + request includes `$_FOO` and the substitutions map doesn’t define `_FOO`). + * Unmatched keys in the parameters list will result in an error (for example, + if a substitutions map defines `_FOO` but the build request doesn't include `$_FOO`). + * To include a literal `$_VARIABLE` in the template, you must escape with `$$`. + * You can explicitly denote variable expansion using the `${_VAR}` syntax. This prevents + ambiguity in cases like `${_FOO}BAR`, where `$_FOO` is a variable. + * The number of parameters is limited to 100 parameters. + * The length of a parameter key and the length of a parameter value + are limited to 100 characters. + +* `trigger_template` - (Optional) Location of the source in a Google +Cloud Source Repository. Structure is documented below. -* `commit_sha` - - (Optional) - Explicit commit SHA to build. +--- The `build` block supports: -* `tags` - - (Optional) - Tags for annotation of a Build. These are not docker tags. +* `images` - (Optional) A list of images to be pushed upon the successful +completion of all build steps. -* `images` - - (Optional) - A list of images to be pushed upon the successful completion of all build steps. - The images are pushed using the builder service account's credentials. - The digests of the pushed images will be stored in the Build resource's results field. - If any of the images fail to be pushed, the build status is marked FAILURE. +* `step` - (Optional) The operations to be performed on the workspace. +Structure is documented below. -* `step` - - (Optional) - The operations to be performed on the workspace. Structure is documented below. +* `tags` - (Optional) Tags for annotation of a build. **These are not docker tags** +--- The `step` block supports: -* `name` - - (Optional) - The name of the container image that will run this particular build step. - If the image is available in the host's Docker daemon's cache, it will be - run directly. If not, the host will attempt to pull the image first, using - the builder service account's credentials if necessary. - The Docker daemon's cache will already have the latest versions of all of - the officially supported build steps (https://github.com/GoogleCloudPlatform/cloud-builders). - The Docker daemon will also have cached many of the layers for some popular - images, like "ubuntu", "debian", but they will be refreshed at the time - you attempt to use them. - If you built an image in a previous build step, it will be stored in the - host's Docker daemon's cache and is available to use as the name for a - later build step. - -* `args` - - (Optional) - A list of arguments that will be presented to the step when it is started. - If the image used to run the step's container has an entrypoint, the args - are used as arguments to that entrypoint. If the image does not define an - entrypoint, the first element in args is used as the entrypoint, and the - remainder will be used as arguments. - -## Attributes Reference - -In addition to the arguments listed above, the following computed attributes are exported: - +* `name` - (Optional) The name of the container image that will run this +particular build step. If the image is available in the host's Docker +daemon's cache, it will be run directly. If not, the host will attempt to +pull the image first, using the builder service account's credentials if +necessary. The Docker daemon's cache will already have the latest versions +of all of the officially supported build steps +(https://github.com/GoogleCloudPlatform/cloud-builders). +The Docker daemon will also have cached many of the layers for some popular +images, like "ubuntu", "debian", but they will be refreshed at the time you +attempt to use them. If you built an image in a previous build step, it will +be stored in the host's Docker daemon's cache and is available to use as +the name for a later build step. + +* `args` - (Optional) A list of arguments that will be presented to the step +when it is started. If the image used to run the step's container has an +entrypoint, the `args` are used as arguments to that entrypoint. If the image +does not define an entrypoint, the first element in args is used as the +entrypoint, and the remainder will be used as arguments. -* `trigger_id` - - The unique identifier for the trigger. - -* `create_time` - - Time when the trigger was created. +--- +The `trigger_template` block supports: -## Timeouts +* `branch_name` - (Optional) Name of the branch to build. -This resource provides the following -[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: +* `commit_sha` - (Optional) Explicit commit SHA to build. -- `create` - Default is 4 minutes. -- `update` - Default is 4 minutes. -- `delete` - Default is 4 minutes. +* `dir` - (Optional) Directory, relative to the source root, in which to run +the build. This must be a relative path. If a step's `dir` is specified and +is an absolute path, this value is ignored for that step's execution. -## Import +* `project` - (Optional) ID of the project that owns the Cloud Source Repository. -Trigger can be imported using any of these accepted formats: +* `repo_name` - (Optional) Name of the Cloud Source Repository. -``` -$ terraform import google_cloudbuild_trigger.default projects/{{project}}/triggers/{{trigger_id}} -$ terraform import google_cloudbuild_trigger.default {{project}}/{{trigger_id}} -$ terraform import google_cloudbuild_trigger.default {{trigger_id}} -``` +* `tag_name` - (Optional) Name of the tag to build. --> If you're importing a resource with beta features, make sure to include `-provider=google-beta` -as an argument so that Terraform uses the correct provider to import your resource. diff --git a/website/docs/r/google_billing_account_iam_binding.md b/website/docs/r/google_billing_account_iam_binding.md index 3a64364e7e..eac0bd9366 100644 --- a/website/docs/r/google_billing_account_iam_binding.md +++ b/website/docs/r/google_billing_account_iam_binding.md @@ -15,6 +15,10 @@ an existing Google Cloud Platform Billing Account. `google_billing_account_iam_member` for the __same role__ or they will fight over what your policy should be. +~> **Note:** On create, this resource will overwrite members of any existing roles. + Use `terraform import` and inspect the `terraform plan` output to ensure + your existing members are preserved. + ## Example Usage ```hcl @@ -23,7 +27,7 @@ resource "google_billing_account_iam_binding" "binding" { role = "roles/billing.viewer" members = [ - "user:jane@example.com", + "user:alice@gmail.com", ] } ``` @@ -36,7 +40,7 @@ The following arguments are supported: * `role` - (Required) The role that should be applied. -* `members` - (Required) A list of users that the role should apply to. +* `members` - (Required) A list of users that the role should apply to. For more details on format and restrictions see https://cloud.google.com/billing/reference/rest/v1/Policy#Binding ## Attributes Reference diff --git a/website/docs/r/google_billing_account_iam_member.md b/website/docs/r/google_billing_account_iam_member.md index 30e4a0d42f..dfe973fefa 100644 --- a/website/docs/r/google_billing_account_iam_member.md +++ b/website/docs/r/google_billing_account_iam_member.md @@ -21,7 +21,7 @@ the IAM policy for an existing Google Cloud Platform Billing Account. resource "google_billing_account_iam_member" "binding" { billing_account_id = "00AA00-000AAA-00AA0A" role = "roles/billing.viewer" - member = "user:jane@example.com" + member = "user:alice@gmail.com" } ``` @@ -33,8 +33,8 @@ The following arguments are supported: * `role` - (Required) The role that should be applied. -* `member` - (Required) The user that the role should apply to. - +* `member` - (Required) The user that the role should apply to. For more details on format and restrictions see https://cloud.google.com/billing/reference/rest/v1/Policy#Binding + ## Attributes Reference In addition to the arguments listed above, the following computed attributes are diff --git a/website/docs/r/google_folder_iam_binding.html.markdown b/website/docs/r/google_folder_iam_binding.html.markdown index a922611525..9589e1aca1 100644 --- a/website/docs/r/google_folder_iam_binding.html.markdown +++ b/website/docs/r/google_folder_iam_binding.html.markdown @@ -15,6 +15,10 @@ an existing Google Cloud Platform folder. `google_folder_iam_policy` or they will fight over what your policy should be. +~> **Note:** On create, this resource will overwrite members of any existing roles. + Use `terraform import` and inspect the `terraform plan` output to ensure + your existing members are preserved. + ## Example Usage ```hcl @@ -28,7 +32,7 @@ resource "google_folder_iam_binding" "admin" { role = "roles/editor" members = [ - "user:jane@example.com", + "user:alice@gmail.com", ] } ``` @@ -41,10 +45,11 @@ The following arguments are supported: * `members` (Required) - An array of identites that will be granted the privilege in the `role`. Each entry can have one of the following values: - * **user:{emailid}**: An email address that represents a specific Google account. For example, alice@gmail.com or joe@example.com. + * **user:{emailid}**: An email address that is associated with a specific Google account. For example, alice@gmail.com. * **serviceAccount:{emailid}**: An email address that represents a service account. For example, my-other-app@appspot.gserviceaccount.com. * **group:{emailid}**: An email address that represents a Google group. For example, admins@example.com. * **domain:{domain}**: A G Suite domain (primary, instead of alias) name that represents all the users of that domain. For example, google.com or example.com. + * For more details on format and restrictions see https://cloud.google.com/billing/reference/rest/v1/Policy#Binding * `role` - (Required) The role that should be applied. Only one `google_folder_iam_binding` can be used per role. Note that custom roles must be of the format diff --git a/website/docs/r/google_folder_iam_member.html.markdown b/website/docs/r/google_folder_iam_member.html.markdown index 3ac0088c2d..3deb8e534d 100644 --- a/website/docs/r/google_folder_iam_member.html.markdown +++ b/website/docs/r/google_folder_iam_member.html.markdown @@ -27,7 +27,7 @@ resource "google_folder" "department1" { resource "google_folder_iam_member" "admin" { folder = "${google_folder.department1.name}" role = "roles/editor" - member = "user:jane@example.com" + member = "user:alice@gmail.com" } ``` @@ -37,7 +37,7 @@ The following arguments are supported: * `folder` - (Required) The resource name of the folder the policy is attached to. Its format is folders/{folder_id}. -* `member` - (Required) The identity that will be granted the privilege in the `role`. +* `member` - (Required) The identity that will be granted the privilege in the `role`. For more details on format and restrictions see https://cloud.google.com/billing/reference/rest/v1/Policy#Binding This field can have one of the following values: * **user:{emailid}**: An email address that represents a specific Google account. For example, alice@gmail.com or joe@example.com. * **serviceAccount:{emailid}**: An email address that represents a service account. For example, my-other-app@appspot.gserviceaccount.com. diff --git a/website/docs/r/google_kms_crypto_key_iam_binding.html.markdown b/website/docs/r/google_kms_crypto_key_iam_binding.html.markdown index cf7f3012e8..f0782cceb2 100644 --- a/website/docs/r/google_kms_crypto_key_iam_binding.html.markdown +++ b/website/docs/r/google_kms_crypto_key_iam_binding.html.markdown @@ -11,6 +11,10 @@ description: |- Allows creation and management of a single binding within IAM policy for an existing Google Cloud KMS crypto key. +~> **Note:** On create, this resource will overwrite members of any existing roles. + Use `terraform import` and inspect the `terraform plan` output to ensure + your existing members are preserved. + ## Example Usage ```hcl @@ -19,7 +23,7 @@ resource "google_kms_crypto_key_iam_binding" "crypto_key" { role = "roles/editor" members = [ - "user:jane@example.com", + "user:alice@gmail.com", ] } ``` @@ -28,7 +32,7 @@ resource "google_kms_crypto_key_iam_binding" "crypto_key" { The following arguments are supported: -* `members` - (Required) A list of users that the role should apply to. +* `members` - (Required) A list of users that the role should apply to. For more details on format and restrictions see https://cloud.google.com/billing/reference/rest/v1/Policy#Binding * `role` - (Required) The role that should be applied. Only one `google_kms_crypto_key_iam_binding` can be used per role. Note that custom roles must be of the format diff --git a/website/docs/r/google_kms_crypto_key_iam_member.html.markdown b/website/docs/r/google_kms_crypto_key_iam_member.html.markdown index 6e8f820fe4..c73b892ad3 100644 --- a/website/docs/r/google_kms_crypto_key_iam_member.html.markdown +++ b/website/docs/r/google_kms_crypto_key_iam_member.html.markdown @@ -22,7 +22,7 @@ the IAM policy for an existing Google Cloud KMS crypto key. resource "google_kms_crypto_key_iam_member" "crypto_key" { crypto_key_id = "your-crypto-key-id" role = "roles/editor" - member = "user:jane@example.com" + member = "user:alice@gmail.com" } ``` @@ -30,7 +30,7 @@ resource "google_kms_crypto_key_iam_member" "crypto_key" { The following arguments are supported: -* `member` - (Required) The user that the role should apply to. +* `member` - (Required) The user that the role should apply to. For more details on format and restrictions see https://cloud.google.com/billing/reference/rest/v1/Policy#Binding * `role` - (Required) The role that should be applied. Note that custom roles must be of the format `[projects|organizations]/{parent-name}/roles/{role-name}`. diff --git a/website/docs/r/google_organization_iam_binding.md b/website/docs/r/google_organization_iam_binding.md index a381f59e1e..be14a12ab8 100644 --- a/website/docs/r/google_organization_iam_binding.md +++ b/website/docs/r/google_organization_iam_binding.md @@ -15,6 +15,10 @@ an existing Google Cloud Platform Organization. `google_organization_iam_member` for the __same role__ or they will fight over what your policy should be. +~> **Note:** On create, this resource will overwrite members of any existing roles. + Use `terraform import` and inspect the `terraform plan` output to ensure + your existing members are preserved. + ## Example Usage ```hcl @@ -23,7 +27,7 @@ resource "google_organization_iam_binding" "binding" { role = "roles/browser" members = [ - "user:jane@example.com", + "user:alice@gmail.com", ] } ``` @@ -38,7 +42,7 @@ The following arguments are supported: `google_organization_iam_binding` can be used per role. Note that custom roles must be of the format `[projects|organizations]/{parent-name}/roles/{role-name}`. -* `members` - (Required) A list of users that the role should apply to. +* `members` - (Required) A list of users that the role should apply to. For more details on format and restrictions see https://cloud.google.com/billing/reference/rest/v1/Policy#Binding ## Attributes Reference diff --git a/website/docs/r/google_organization_iam_member.md b/website/docs/r/google_organization_iam_member.md index 5bfd2ee7d0..77f6db8ae5 100644 --- a/website/docs/r/google_organization_iam_member.md +++ b/website/docs/r/google_organization_iam_member.md @@ -21,7 +21,7 @@ the IAM policy for an existing Google Cloud Platform Organization. resource "google_organization_iam_member" "binding" { org_id = "0123456789" role = "roles/editor" - member = "user:jane@example.com" + member = "user:alice@gmail.com" } ``` @@ -34,8 +34,8 @@ The following arguments are supported: * `role` - (Required) The role that should be applied. Note that custom roles must be of the format `[projects|organizations]/{parent-name}/roles/{role-name}`. -* `member` - (Required) The user that the role should apply to. - +* `member` - (Required) The user that the role should apply to. For more details on format and restrictions see https://cloud.google.com/billing/reference/rest/v1/Policy#Binding + ## Attributes Reference In addition to the arguments listed above, the following computed attributes are diff --git a/website/docs/version_2_upgrade.html.markdown b/website/docs/version_2_upgrade.html.markdown index e8a67b46f1..2cdbc94599 100644 --- a/website/docs/version_2_upgrade.html.markdown +++ b/website/docs/version_2_upgrade.html.markdown @@ -53,6 +53,7 @@ Upgrade topics: - [Resource: `google_sql_database_instance`](#resource-google_sql_database_instance) - [Resource: `google_storage_default_object_acl`](#resource-google_storage_default_object_acl) - [Resource: `google_storage_object_acl`](#resource-google_storage_object_acl) +- [Resource: `google_*_iam_binding`](#google_*_iam_binding) @@ -623,3 +624,31 @@ values that were added outside of Terraform should be added to the config. Terraform will remove values not explicitly set in this field. Any `role_entity` values that were added outside of Terraform should be added to the config. For fine-grained management, use `google_storage_object_access_control`. + +## Resource: `google_*_iam_binding` + +### Create is now authoritative + +Every `iam_binding` resource will overwrite the existing member list for a given +role on Create. Running `terraform plan` for the first time will not show members +that have been added via other tools. *To ensure existing `members` are preserved +use `terraform import` instead of creating the resource.* + +Previous versions of `google_*_iam_binding` resources would merge the existing +members of a role with the members defined in the terraform config. If there was +a difference between the members defined in the config and the existing members +defined for an existing role it would show a diff if `terraform plan` was run +immediately after create had succeeded. + +Affected resources: +* `google_billing_account_iam_binding` +* `google_folder_iam_binding` +* `google_kms_key_ring_iam_binding` +* `google_kms_crypto_key_iam_binding` +* `google_spanner_instance_iam_binding` +* `google_spanner_database_iam_binding` +* `google_organization_iam_binding` +* `google_project_iam_binding` +* `google_pubsub_topic_iam_binding` +* `google_pubsub_subscription_iam_binding` +* `google_service_account_iam_binding`