// 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()) } }