diff --git a/.changelog/7083.txt b/.changelog/7083.txt new file mode 100644 index 0000000000..0d34bef7f5 --- /dev/null +++ b/.changelog/7083.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +`google_cloudbuild_bitbucket_server_config` +``` diff --git a/google-beta/cloud_build_operation.go b/google-beta/cloud_build_operation.go new file mode 100644 index 0000000000..8fc3aa0f55 --- /dev/null +++ b/google-beta/cloud_build_operation.go @@ -0,0 +1,75 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// 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 ( + "encoding/json" + "fmt" + "time" +) + +type CloudBuildOperationWaiter struct { + Config *Config + UserAgent string + Project string + CommonOperationWaiter +} + +func (w *CloudBuildOperationWaiter) QueryOp() (interface{}, error) { + if w == nil { + return nil, fmt.Errorf("Cannot query operation, it's unset or nil.") + } + // Returns the proper get. + url := fmt.Sprintf("%s%s", w.Config.CloudBuildBasePath, w.CommonOperationWaiter.Op.Name) + + return sendRequest(w.Config, "GET", w.Project, url, w.UserAgent, nil) +} + +func createCloudBuildWaiter(config *Config, op map[string]interface{}, project, activity, userAgent string) (*CloudBuildOperationWaiter, error) { + w := &CloudBuildOperationWaiter{ + Config: config, + UserAgent: userAgent, + Project: project, + } + if err := w.CommonOperationWaiter.SetOp(op); err != nil { + return nil, err + } + return w, nil +} + +// nolint: deadcode,unused +func cloudBuildOperationWaitTimeWithResponse(config *Config, op map[string]interface{}, response *map[string]interface{}, project, activity, userAgent string, timeout time.Duration) error { + w, err := createCloudBuildWaiter(config, op, project, activity, userAgent) + if err != nil { + return err + } + if err := OperationWait(w, activity, timeout, config.PollInterval); err != nil { + return err + } + return json.Unmarshal([]byte(w.CommonOperationWaiter.Op.Response), response) +} + +func cloudBuildOperationWaitTime(config *Config, op map[string]interface{}, project, activity, userAgent string, timeout time.Duration) error { + if val, ok := op["name"]; !ok || val == "" { + // This was a synchronous call - there is no operation to wait for. + return nil + } + w, err := createCloudBuildWaiter(config, op, project, activity, userAgent) + if err != nil { + // If w is nil, the op was synchronous. + return err + } + return OperationWait(w, activity, timeout, config.PollInterval) +} diff --git a/google-beta/provider.go b/google-beta/provider.go index 2d4c242859..22d76e3d58 100644 --- a/google-beta/provider.go +++ b/google-beta/provider.go @@ -1108,9 +1108,9 @@ func Provider() *schema.Provider { return provider } -// Generated resources: 300 +// Generated resources: 301 // Generated IAM resources: 204 -// Total generated resources: 504 +// Total generated resources: 505 func ResourceMap() map[string]*schema.Resource { resourceMap, _ := ResourceMapWithErrors() return resourceMap @@ -1217,6 +1217,7 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { "google_cloud_asset_folder_feed": resourceCloudAssetFolderFeed(), "google_cloud_asset_organization_feed": resourceCloudAssetOrganizationFeed(), "google_cloud_asset_project_feed": resourceCloudAssetProjectFeed(), + "google_cloudbuild_bitbucket_server_config": resourceCloudBuildBitbucketServerConfig(), "google_cloudbuild_trigger": resourceCloudBuildTrigger(), "google_cloudbuildv2_connection_iam_binding": ResourceIamBinding(Cloudbuildv2ConnectionIamSchema, Cloudbuildv2ConnectionIamUpdaterProducer, Cloudbuildv2ConnectionIdParseFunc), "google_cloudbuildv2_connection_iam_member": ResourceIamMember(Cloudbuildv2ConnectionIamSchema, Cloudbuildv2ConnectionIamUpdaterProducer, Cloudbuildv2ConnectionIdParseFunc), diff --git a/google-beta/resource_cloudbuild_bitbucket_server_config.go b/google-beta/resource_cloudbuild_bitbucket_server_config.go new file mode 100644 index 0000000000..3087c967e7 --- /dev/null +++ b/google-beta/resource_cloudbuild_bitbucket_server_config.go @@ -0,0 +1,846 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// 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" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceCloudBuildBitbucketServerConfig() *schema.Resource { + return &schema.Resource{ + Create: resourceCloudBuildBitbucketServerConfigCreate, + Read: resourceCloudBuildBitbucketServerConfigRead, + Update: resourceCloudBuildBitbucketServerConfigUpdate, + Delete: resourceCloudBuildBitbucketServerConfigDelete, + + Importer: &schema.ResourceImporter{ + State: resourceCloudBuildBitbucketServerConfigImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(20 * time.Minute), + Update: schema.DefaultTimeout(20 * time.Minute), + Delete: schema.DefaultTimeout(20 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "api_key": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `Immutable. API Key that will be attached to webhook. Once this field has been set, it cannot be changed. +Changing this field will result in deleting/ recreating the resource.`, + }, + "config_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The ID to use for the BitbucketServerConfig, which will become the final component of the BitbucketServerConfig's resource name.`, + }, + "host_uri": { + Type: schema.TypeString, + Required: true, + Description: `Immutable. The URI of the Bitbucket Server host. Once this field has been set, it cannot be changed. +If you need to change it, please create another BitbucketServerConfig.`, + }, + "location": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The location of this bitbucket server config.`, + }, + "secrets": { + Type: schema.TypeList, + Required: true, + Description: `Secret Manager secrets needed by the config.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "admin_access_token_version_name": { + Type: schema.TypeString, + Required: true, + Description: `The resource name for the admin access token's secret version.`, + }, + "read_access_token_version_name": { + Type: schema.TypeString, + Required: true, + Description: `The resource name for the read access token's secret version.`, + }, + "webhook_secret_version_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `Immutable. The resource name for the webhook secret's secret version. Once this field has been set, it cannot be changed. +Changing this field will result in deleting/ recreating the resource.`, + }, + }, + }, + }, + "username": { + Type: schema.TypeString, + Required: true, + Description: `Username of the account Cloud Build will use on Bitbucket Server.`, + }, + "connected_repositories": { + Type: schema.TypeSet, + Optional: true, + Description: `Connected Bitbucket Server repositories for this config.`, + Elem: cloudbuildBitbucketServerConfigConnectedRepositoriesSchema(), + // Default schema.HashSchema is used. + }, + "peered_network": { + Type: schema.TypeString, + Optional: true, + Description: `The network to be used when reaching out to the Bitbucket Server instance. The VPC network must be enabled for private service connection. +This should be set if the Bitbucket Server instance is hosted on-premises and not reachable by public internet. If this field is left empty, +no network peering will occur and calls to the Bitbucket Server instance will be made over the public internet. Must be in the format +projects/{project}/global/networks/{network}, where {project} is a project number or id and {network} is the name of a VPC network in the project.`, + }, + "ssl_ca": { + Type: schema.TypeString, + Optional: true, + Description: `SSL certificate to use for requests to Bitbucket Server. The format should be PEM format but the extension can be one of .pem, .cer, or .crt.`, + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: `The resource name for the config.`, + }, + "webhook_key": { + Type: schema.TypeString, + Computed: true, + Description: `Output only. UUID included in webhook requests. The UUID is used to look up the corresponding config.`, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + UseJSONNumber: true, + } +} + +func cloudbuildBitbucketServerConfigConnectedRepositoriesSchema() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "project_key": { + Type: schema.TypeString, + Required: true, + Description: `Identifier for the project storing the repository.`, + }, + "repo_slug": { + Type: schema.TypeString, + Required: true, + Description: `Identifier for the repository.`, + }, + }, + } +} + +func resourceCloudBuildBitbucketServerConfigCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + obj := make(map[string]interface{}) + hostUriProp, err := expandCloudBuildBitbucketServerConfigHostUri(d.Get("host_uri"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("host_uri"); !isEmptyValue(reflect.ValueOf(hostUriProp)) && (ok || !reflect.DeepEqual(v, hostUriProp)) { + obj["hostUri"] = hostUriProp + } + secretsProp, err := expandCloudBuildBitbucketServerConfigSecrets(d.Get("secrets"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("secrets"); !isEmptyValue(reflect.ValueOf(secretsProp)) && (ok || !reflect.DeepEqual(v, secretsProp)) { + obj["secrets"] = secretsProp + } + usernameProp, err := expandCloudBuildBitbucketServerConfigUsername(d.Get("username"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("username"); !isEmptyValue(reflect.ValueOf(usernameProp)) && (ok || !reflect.DeepEqual(v, usernameProp)) { + obj["username"] = usernameProp + } + apiKeyProp, err := expandCloudBuildBitbucketServerConfigApiKey(d.Get("api_key"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("api_key"); !isEmptyValue(reflect.ValueOf(apiKeyProp)) && (ok || !reflect.DeepEqual(v, apiKeyProp)) { + obj["apiKey"] = apiKeyProp + } + connectedRepositoriesProp, err := expandCloudBuildBitbucketServerConfigConnectedRepositories(d.Get("connected_repositories"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("connected_repositories"); !isEmptyValue(reflect.ValueOf(connectedRepositoriesProp)) && (ok || !reflect.DeepEqual(v, connectedRepositoriesProp)) { + obj["connectedRepositories"] = connectedRepositoriesProp + } + peeredNetworkProp, err := expandCloudBuildBitbucketServerConfigPeeredNetwork(d.Get("peered_network"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("peered_network"); !isEmptyValue(reflect.ValueOf(peeredNetworkProp)) && (ok || !reflect.DeepEqual(v, peeredNetworkProp)) { + obj["peeredNetwork"] = peeredNetworkProp + } + sslCaProp, err := expandCloudBuildBitbucketServerConfigSslCa(d.Get("ssl_ca"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("ssl_ca"); !isEmptyValue(reflect.ValueOf(sslCaProp)) && (ok || !reflect.DeepEqual(v, sslCaProp)) { + obj["sslCa"] = sslCaProp + } + + obj, err = resourceCloudBuildBitbucketServerConfigEncoder(d, meta, obj) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{CloudBuildBasePath}}projects/{{project}}/locations/{{location}}/bitbucketServerConfigs?bitbucketServerConfigId={{config_id}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new BitbucketServerConfig: %#v", obj) + billingProject := "" + + project, err := getProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for BitbucketServerConfig: %s", err) + } + billingProject = project + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequestWithTimeout(config, "POST", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return fmt.Errorf("Error creating BitbucketServerConfig: %s", err) + } + + // Store the ID now + id, err := replaceVars(d, config, "projects/{{project}}/locations/{{location}}/bitbucketServerConfigs/{{config_id}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + // Use the resource in the operation response to populate + // identity fields and d.Id() before read + var opRes map[string]interface{} + err = cloudBuildOperationWaitTimeWithResponse( + config, res, &opRes, project, "Creating BitbucketServerConfig", userAgent, + d.Timeout(schema.TimeoutCreate)) + if err != nil { + // The resource didn't actually create + d.SetId("") + + return fmt.Errorf("Error waiting to create BitbucketServerConfig: %s", err) + } + + if err := d.Set("name", flattenCloudBuildBitbucketServerConfigName(opRes["name"], d, config)); err != nil { + return err + } + + // This may have caused the ID to update - update it if so. + id, err = replaceVars(d, config, "projects/{{project}}/locations/{{location}}/bitbucketServerConfigs/{{config_id}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating BitbucketServerConfig without connected repos: %q: %#v", d.Id(), res) + + if v, ok := d.GetOkExists("connected_repositories"); !isEmptyValue(reflect.ValueOf(connectedRepositoriesProp)) && (ok || !reflect.DeepEqual(v, connectedRepositoriesProp)) { + connectedReposPropArray, ok := connectedRepositoriesProp.([]interface{}) + if !ok { + return fmt.Errorf("Error reading connected_repositories") + } + + requests := make([]interface{}, len(connectedReposPropArray)) + for i := 0; i < len(connectedReposPropArray); i++ { + connectedRepo := make(map[string]interface{}) + connectedRepo["parent"] = id + connectedRepo["repo"] = connectedReposPropArray[i] + + connectedRepoRequest := make(map[string]interface{}) + connectedRepoRequest["parent"] = id + connectedRepoRequest["bitbucketServerConnectedRepository"] = connectedRepo + + requests[i] = connectedRepoRequest + } + obj = make(map[string]interface{}) + obj["requests"] = requests + + url, err = replaceVars(d, config, "{{CloudBuildBasePath}}projects/{{project}}/locations/{{location}}/bitbucketServerConfigs/{{config_id}}/connectedRepositories:batchCreate") + if err != nil { + return err + } + + res, err = sendRequestWithTimeout(config, "POST", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return fmt.Errorf("Error creating connected_repositories: %s", err) + } + + err = cloudBuildOperationWaitTime( + config, res, project, "Creating connected_repositories on BitbucketServerConfig", userAgent, + d.Timeout(schema.TimeoutCreate)) + if err != nil { + return fmt.Errorf("Error waiting to create connected_repositories: %s", err) + } + } else { + log.Printf("[DEBUG] No connected repositories found to create: %#v", connectedRepositoriesProp) + } + + log.Printf("[DEBUG] Finished creating BitbucketServerConfig %q: %#v", d.Id(), res) + + return resourceCloudBuildBitbucketServerConfigRead(d, meta) +} + +func resourceCloudBuildBitbucketServerConfigRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{CloudBuildBasePath}}projects/{{project}}/locations/{{location}}/bitbucketServerConfigs/{{config_id}}") + if err != nil { + return err + } + + billingProject := "" + + project, err := getProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for BitbucketServerConfig: %s", err) + } + billingProject = project + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequest(config, "GET", billingProject, url, userAgent, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("CloudBuildBitbucketServerConfig %q", d.Id())) + } + + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error reading BitbucketServerConfig: %s", err) + } + + if err := d.Set("name", flattenCloudBuildBitbucketServerConfigName(res["name"], d, config)); err != nil { + return fmt.Errorf("Error reading BitbucketServerConfig: %s", err) + } + if err := d.Set("host_uri", flattenCloudBuildBitbucketServerConfigHostUri(res["hostUri"], d, config)); err != nil { + return fmt.Errorf("Error reading BitbucketServerConfig: %s", err) + } + if err := d.Set("secrets", flattenCloudBuildBitbucketServerConfigSecrets(res["secrets"], d, config)); err != nil { + return fmt.Errorf("Error reading BitbucketServerConfig: %s", err) + } + if err := d.Set("username", flattenCloudBuildBitbucketServerConfigUsername(res["username"], d, config)); err != nil { + return fmt.Errorf("Error reading BitbucketServerConfig: %s", err) + } + if err := d.Set("webhook_key", flattenCloudBuildBitbucketServerConfigWebhookKey(res["webhookKey"], d, config)); err != nil { + return fmt.Errorf("Error reading BitbucketServerConfig: %s", err) + } + if err := d.Set("api_key", flattenCloudBuildBitbucketServerConfigApiKey(res["apiKey"], d, config)); err != nil { + return fmt.Errorf("Error reading BitbucketServerConfig: %s", err) + } + if err := d.Set("connected_repositories", flattenCloudBuildBitbucketServerConfigConnectedRepositories(res["connectedRepositories"], d, config)); err != nil { + return fmt.Errorf("Error reading BitbucketServerConfig: %s", err) + } + if err := d.Set("peered_network", flattenCloudBuildBitbucketServerConfigPeeredNetwork(res["peeredNetwork"], d, config)); err != nil { + return fmt.Errorf("Error reading BitbucketServerConfig: %s", err) + } + if err := d.Set("ssl_ca", flattenCloudBuildBitbucketServerConfigSslCa(res["sslCa"], d, config)); err != nil { + return fmt.Errorf("Error reading BitbucketServerConfig: %s", err) + } + + return nil +} + +func resourceCloudBuildBitbucketServerConfigUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + billingProject := "" + + project, err := getProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for BitbucketServerConfig: %s", err) + } + billingProject = project + + obj := make(map[string]interface{}) + hostUriProp, err := expandCloudBuildBitbucketServerConfigHostUri(d.Get("host_uri"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("host_uri"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, hostUriProp)) { + obj["hostUri"] = hostUriProp + } + secretsProp, err := expandCloudBuildBitbucketServerConfigSecrets(d.Get("secrets"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("secrets"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, secretsProp)) { + obj["secrets"] = secretsProp + } + usernameProp, err := expandCloudBuildBitbucketServerConfigUsername(d.Get("username"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("username"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, usernameProp)) { + obj["username"] = usernameProp + } + connectedRepositoriesProp, err := expandCloudBuildBitbucketServerConfigConnectedRepositories(d.Get("connected_repositories"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("connected_repositories"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, connectedRepositoriesProp)) { + obj["connectedRepositories"] = connectedRepositoriesProp + } + peeredNetworkProp, err := expandCloudBuildBitbucketServerConfigPeeredNetwork(d.Get("peered_network"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("peered_network"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, peeredNetworkProp)) { + obj["peeredNetwork"] = peeredNetworkProp + } + sslCaProp, err := expandCloudBuildBitbucketServerConfigSslCa(d.Get("ssl_ca"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("ssl_ca"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, sslCaProp)) { + obj["sslCa"] = sslCaProp + } + + obj, err = resourceCloudBuildBitbucketServerConfigEncoder(d, meta, obj) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{CloudBuildBasePath}}projects/{{project}}/locations/{{location}}/bitbucketServerConfigs/{{config_id}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating BitbucketServerConfig %q: %#v", d.Id(), obj) + updateMask := []string{} + + if d.HasChange("host_uri") { + updateMask = append(updateMask, "hostUri") + } + + if d.HasChange("secrets") { + updateMask = append(updateMask, "secrets") + } + + if d.HasChange("username") { + updateMask = append(updateMask, "username") + } + + if d.HasChange("connected_repositories") { + updateMask = append(updateMask, "connectedRepositories") + } + + if d.HasChange("peered_network") { + updateMask = append(updateMask, "peeredNetwork") + } + + if d.HasChange("ssl_ca") { + updateMask = append(updateMask, "sslCa") + } + // updateMask is a URL parameter but not present in the schema, so replaceVars + // won't set it + url, err = addQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")}) + if err != nil { + return err + } + // remove connectedRepositories from updateMask + for i, field := range updateMask { + if field == "connectedRepositories" { + updateMask = append(updateMask[:i], updateMask[i+1:]...) + break + } + } + // reconstruct url + url, err = replaceVars(d, config, "{{CloudBuildBasePath}}projects/{{project}}/locations/{{location}}/bitbucketServerConfigs/{{config_id}}") + if err != nil { + return err + } + url, err = addQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")}) + if err != nil { + return err + } + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequestWithTimeout(config, "PATCH", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return fmt.Errorf("Error updating BitbucketServerConfig %q: %s", d.Id(), err) + } else { + log.Printf("[DEBUG] Finished updating BitbucketServerConfig %q: %#v", d.Id(), res) + } + + err = cloudBuildOperationWaitTime( + config, res, project, "Updating BitbucketServerConfig", userAgent, + d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return err + } + + if d.HasChange("connected_repositories") { + o, n := d.GetChange("connected_repositories") + oReposSet, ok := o.(*schema.Set) + if !ok { + return fmt.Errorf("Error reading old connected repositories") + } + nReposSet, ok := n.(*schema.Set) + if !ok { + return fmt.Errorf("Error reading new connected repositories") + } + + removeRepos := oReposSet.Difference(nReposSet).List() + createRepos := nReposSet.Difference(oReposSet).List() + + url, err = replaceVars(d, config, "{{CloudBuildBasePath}}projects/{{project}}/locations/{{location}}/bitbucketServerConfigs/{{config_id}}:removeBitbucketServerConnectedRepository") + if err != nil { + return err + } + + // send remove repo requests. + for _, repo := range removeRepos { + obj := make(map[string]interface{}) + obj["connectedRepository"] = repo + res, err = sendRequestWithTimeout(config, "POST", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return fmt.Errorf("Error removing connected_repositories: %s", err) + } + } + + // if repos to create, prepare and send batchCreate request + if len(createRepos) > 0 { + parent, err := replaceVars(d, config, "projects/{{project}}/locations/{{location}}/bitbucketServerConfigs/{{config_id}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + var requests []interface{} + for _, repo := range createRepos { + connectedRepo := make(map[string]interface{}) + connectedRepo["parent"] = parent + connectedRepo["repo"] = repo + + connectedRepoRequest := make(map[string]interface{}) + connectedRepoRequest["parent"] = parent + connectedRepoRequest["bitbucketServerConnectedRepository"] = connectedRepo + + requests = append(requests, connectedRepoRequest) + } + obj = make(map[string]interface{}) + obj["requests"] = requests + + url, err = replaceVars(d, config, "{{CloudBuildBasePath}}projects/{{project}}/locations/{{location}}/bitbucketServerConfigs/{{config_id}}/connectedRepositories:batchCreate") + if err != nil { + return err + } + + res, err = sendRequestWithTimeout(config, "POST", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return fmt.Errorf("Error creating connected_repositories: %s", err) + } + + err = cloudBuildOperationWaitTime( + config, res, project, "Updating connected_repositories on BitbucketServerConfig", userAgent, + d.Timeout(schema.TimeoutUpdate)) + if err != nil { + return fmt.Errorf("Error waiting to create connected_repositories: %s", err) + } + } + } else { + log.Printf("[DEBUG] connected_repositories have no changes") + } + return resourceCloudBuildBitbucketServerConfigRead(d, meta) +} + +func resourceCloudBuildBitbucketServerConfigDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + billingProject := "" + + project, err := getProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for BitbucketServerConfig: %s", err) + } + billingProject = project + + url, err := replaceVars(d, config, "{{CloudBuildBasePath}}projects/{{project}}/locations/{{location}}/bitbucketServerConfigs/{{config_id}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting BitbucketServerConfig %q", d.Id()) + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequestWithTimeout(config, "DELETE", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return handleNotFoundError(err, d, "BitbucketServerConfig") + } + + err = cloudBuildOperationWaitTime( + config, res, project, "Deleting BitbucketServerConfig", userAgent, + d.Timeout(schema.TimeoutDelete)) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Finished deleting BitbucketServerConfig %q: %#v", d.Id(), res) + return nil +} + +func resourceCloudBuildBitbucketServerConfigImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + if err := parseImportId([]string{ + "projects/(?P[^/]+)/locations/(?P[^/]+)/bitbucketServerConfigs/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := replaceVars(d, config, "projects/{{project}}/locations/{{location}}/bitbucketServerConfigs/{{config_id}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenCloudBuildBitbucketServerConfigName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudBuildBitbucketServerConfigHostUri(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudBuildBitbucketServerConfigSecrets(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["admin_access_token_version_name"] = + flattenCloudBuildBitbucketServerConfigSecretsAdminAccessTokenVersionName(original["adminAccessTokenVersionName"], d, config) + transformed["read_access_token_version_name"] = + flattenCloudBuildBitbucketServerConfigSecretsReadAccessTokenVersionName(original["readAccessTokenVersionName"], d, config) + transformed["webhook_secret_version_name"] = + flattenCloudBuildBitbucketServerConfigSecretsWebhookSecretVersionName(original["webhookSecretVersionName"], d, config) + return []interface{}{transformed} +} +func flattenCloudBuildBitbucketServerConfigSecretsAdminAccessTokenVersionName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudBuildBitbucketServerConfigSecretsReadAccessTokenVersionName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudBuildBitbucketServerConfigSecretsWebhookSecretVersionName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudBuildBitbucketServerConfigUsername(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudBuildBitbucketServerConfigWebhookKey(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudBuildBitbucketServerConfigApiKey(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudBuildBitbucketServerConfigConnectedRepositories(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return v + } + l := v.([]interface{}) + transformed := schema.NewSet(schema.HashResource(cloudbuildBitbucketServerConfigConnectedRepositoriesSchema()), []interface{}{}) + 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.Add(map[string]interface{}{ + "project_key": flattenCloudBuildBitbucketServerConfigConnectedRepositoriesProjectKey(original["projectKey"], d, config), + "repo_slug": flattenCloudBuildBitbucketServerConfigConnectedRepositoriesRepoSlug(original["repoSlug"], d, config), + }) + } + return transformed +} +func flattenCloudBuildBitbucketServerConfigConnectedRepositoriesProjectKey(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudBuildBitbucketServerConfigConnectedRepositoriesRepoSlug(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudBuildBitbucketServerConfigPeeredNetwork(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudBuildBitbucketServerConfigSslCa(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func expandCloudBuildBitbucketServerConfigHostUri(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudBuildBitbucketServerConfigSecrets(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedAdminAccessTokenVersionName, err := expandCloudBuildBitbucketServerConfigSecretsAdminAccessTokenVersionName(original["admin_access_token_version_name"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedAdminAccessTokenVersionName); val.IsValid() && !isEmptyValue(val) { + transformed["adminAccessTokenVersionName"] = transformedAdminAccessTokenVersionName + } + + transformedReadAccessTokenVersionName, err := expandCloudBuildBitbucketServerConfigSecretsReadAccessTokenVersionName(original["read_access_token_version_name"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedReadAccessTokenVersionName); val.IsValid() && !isEmptyValue(val) { + transformed["readAccessTokenVersionName"] = transformedReadAccessTokenVersionName + } + + transformedWebhookSecretVersionName, err := expandCloudBuildBitbucketServerConfigSecretsWebhookSecretVersionName(original["webhook_secret_version_name"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedWebhookSecretVersionName); val.IsValid() && !isEmptyValue(val) { + transformed["webhookSecretVersionName"] = transformedWebhookSecretVersionName + } + + return transformed, nil +} + +func expandCloudBuildBitbucketServerConfigSecretsAdminAccessTokenVersionName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudBuildBitbucketServerConfigSecretsReadAccessTokenVersionName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudBuildBitbucketServerConfigSecretsWebhookSecretVersionName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudBuildBitbucketServerConfigUsername(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudBuildBitbucketServerConfigApiKey(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudBuildBitbucketServerConfigConnectedRepositories(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + v = v.(*schema.Set).List() + 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{}) + + transformedProjectKey, err := expandCloudBuildBitbucketServerConfigConnectedRepositoriesProjectKey(original["project_key"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedProjectKey); val.IsValid() && !isEmptyValue(val) { + transformed["projectKey"] = transformedProjectKey + } + + transformedRepoSlug, err := expandCloudBuildBitbucketServerConfigConnectedRepositoriesRepoSlug(original["repo_slug"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedRepoSlug); val.IsValid() && !isEmptyValue(val) { + transformed["repoSlug"] = transformedRepoSlug + } + + req = append(req, transformed) + } + return req, nil +} + +func expandCloudBuildBitbucketServerConfigConnectedRepositoriesProjectKey(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudBuildBitbucketServerConfigConnectedRepositoriesRepoSlug(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudBuildBitbucketServerConfigPeeredNetwork(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudBuildBitbucketServerConfigSslCa(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func resourceCloudBuildBitbucketServerConfigEncoder(d *schema.ResourceData, meta interface{}, obj map[string]interface{}) (map[string]interface{}, error) { + // connectedRepositories is needed for batchCreate on the config after creation. + delete(obj, "connectedRepositories") + return obj, nil +} diff --git a/google-beta/resource_cloudbuild_bitbucket_server_config_generated_test.go b/google-beta/resource_cloudbuild_bitbucket_server_config_generated_test.go new file mode 100644 index 0000000000..c0de8b3197 --- /dev/null +++ b/google-beta/resource_cloudbuild_bitbucket_server_config_generated_test.go @@ -0,0 +1,172 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// 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-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccCloudBuildBitbucketServerConfig_cloudbuildBitbucketServerConfigExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudBuildBitbucketServerConfigDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccCloudBuildBitbucketServerConfig_cloudbuildBitbucketServerConfigExample(context), + }, + { + ResourceName: "google_cloudbuild_bitbucket_server_config.bbs-config", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"config_id", "location"}, + }, + }, + }) +} + +func testAccCloudBuildBitbucketServerConfig_cloudbuildBitbucketServerConfigExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_cloudbuild_bitbucket_server_config" "bbs-config" { + config_id = "mybbsconfig" + location = "us-central1" + host_uri = "https://bbs.com" + secrets { + admin_access_token_version_name = "projects/myProject/secrets/mybbspat/versions/1" + read_access_token_version_name = "projects/myProject/secrets/mybbspat/versions/1" + webhook_secret_version_name = "projects/myProject/secrets/mybbspat/versions/1" + } + username = "test" + api_key = "" +} +`, context) +} + +func TestAccCloudBuildBitbucketServerConfig_cloudbuildBitbucketServerConfigPeeredNetworkExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "network_name": BootstrapSharedTestNetwork(t, "peered-network"), + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudBuildBitbucketServerConfigDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccCloudBuildBitbucketServerConfig_cloudbuildBitbucketServerConfigPeeredNetworkExample(context), + }, + { + ResourceName: "google_cloudbuild_bitbucket_server_config.bbs-config-with-peered-network", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"config_id", "location"}, + }, + }, + }) +} + +func testAccCloudBuildBitbucketServerConfig_cloudbuildBitbucketServerConfigPeeredNetworkExample(context map[string]interface{}) string { + return Nprintf(` +data "google_project" "project" {} + +resource "google_project_service" "servicenetworking" { + service = "servicenetworking.googleapis.com" + disable_on_destroy = false +} + +data "google_compute_network" "vpc_network" { + name = "%{network_name}" + depends_on = [google_project_service.servicenetworking] +} + +resource "google_compute_global_address" "private_ip_alloc" { + name = "private-ip-alloc" + purpose = "VPC_PEERING" + address_type = "INTERNAL" + prefix_length = 16 + network = data.google_compute_network.vpc_network.id +} + +resource "google_service_networking_connection" "default" { + network = data.google_compute_network.vpc_network.id + service = "servicenetworking.googleapis.com" + reserved_peering_ranges = [google_compute_global_address.private_ip_alloc.name] + depends_on = [google_project_service.servicenetworking] +} + +resource "google_cloudbuild_bitbucket_server_config" "bbs-config-with-peered-network" { + config_id = "mybbsconfig" + location = "us-central1" + host_uri = "https://bbs.com" + secrets { + admin_access_token_version_name = "projects/myProject/secrets/mybbspat/versions/1" + read_access_token_version_name = "projects/myProject/secrets/mybbspat/versions/1" + webhook_secret_version_name = "projects/myProject/secrets/mybbspat/versions/1" + } + username = "test" + api_key = "" + peered_network = replace(data.google_compute_network.vpc_network.id, data.google_project.project.name, data.google_project.project.number) + ssl_ca = "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----\n" + depends_on = [google_service_networking_connection.default] +} +`, context) +} + +func testAccCheckCloudBuildBitbucketServerConfigDestroyProducer(t *testing.T) func(s *terraform.State) error { + return func(s *terraform.State) error { + for name, rs := range s.RootModule().Resources { + if rs.Type != "google_cloudbuild_bitbucket_server_config" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := googleProviderConfig(t) + + url, err := replaceVarsForTest(config, rs, "{{CloudBuildBasePath}}projects/{{project}}/locations/{{location}}/bitbucketServerConfigs/{{config_id}}") + if err != nil { + return err + } + + billingProject := "" + + if config.BillingProject != "" { + billingProject = config.BillingProject + } + + _, err = sendRequest(config, "GET", billingProject, url, config.userAgent, nil) + if err == nil { + return fmt.Errorf("CloudBuildBitbucketServerConfig still exists at %s", url) + } + } + + return nil + } +} diff --git a/google-beta/resource_cloudbuild_bitbucket_server_config_sweeper_test.go b/google-beta/resource_cloudbuild_bitbucket_server_config_sweeper_test.go new file mode 100644 index 0000000000..6107437ad8 --- /dev/null +++ b/google-beta/resource_cloudbuild_bitbucket_server_config_sweeper_test.go @@ -0,0 +1,128 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// 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 ( + "context" + "log" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func init() { + resource.AddTestSweepers("CloudBuildBitbucketServerConfig", &resource.Sweeper{ + Name: "CloudBuildBitbucketServerConfig", + F: testSweepCloudBuildBitbucketServerConfig, + }) +} + +// At the time of writing, the CI only passes us-central1 as the region +func testSweepCloudBuildBitbucketServerConfig(region string) error { + resourceName := "CloudBuildBitbucketServerConfig" + log.Printf("[INFO][SWEEPER_LOG] Starting sweeper for %s", resourceName) + + config, err := sharedConfigForRegion(region) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error getting shared config for region: %s", err) + return err + } + + err = config.LoadAndValidate(context.Background()) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error loading: %s", err) + return err + } + + t := &testing.T{} + billingId := getTestBillingAccountFromEnv(t) + + // Setup variables to replace in list template + d := &ResourceDataMock{ + FieldsInSchema: map[string]interface{}{ + "project": config.Project, + "region": region, + "location": region, + "zone": "-", + "billing_account": billingId, + }, + } + + listTemplate := strings.Split("https://cloudbuild.googleapis.com/v1/projects/{{project}}/locations/{{location}}/bitbucketServerConfigs", "?")[0] + listUrl, err := replaceVars(d, config, listTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing sweeper list url: %s", err) + return nil + } + + res, err := sendRequest(config, "GET", config.Project, listUrl, config.userAgent, nil) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error in response from request %s: %s", listUrl, err) + return nil + } + + resourceList, ok := res["bitbucketServerConfigs"] + if !ok { + log.Printf("[INFO][SWEEPER_LOG] Nothing found in response.") + return nil + } + + rl := resourceList.([]interface{}) + + log.Printf("[INFO][SWEEPER_LOG] Found %d items in %s list response.", len(rl), resourceName) + // Keep count of items that aren't sweepable for logging. + nonPrefixCount := 0 + for _, ri := range rl { + obj := ri.(map[string]interface{}) + var name string + // Id detected in the delete URL, attempt to use id. + if obj["id"] != nil { + name = GetResourceNameFromSelfLink(obj["id"].(string)) + } else if obj["name"] != nil { + name = GetResourceNameFromSelfLink(obj["name"].(string)) + } else { + log.Printf("[INFO][SWEEPER_LOG] %s resource name and id were nil", resourceName) + return nil + } + // Skip resources that shouldn't be sweeped + if !isSweepableTestResource(name) { + nonPrefixCount++ + continue + } + + deleteTemplate := "https://cloudbuild.googleapis.com/v1/projects/{{project}}/locations/{{location}}/bitbucketServerConfigs/{{config_id}}" + deleteUrl, err := replaceVars(d, config, deleteTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing delete url: %s", err) + return nil + } + deleteUrl = deleteUrl + name + + // Don't wait on operations as we may have a lot to delete + _, err = sendRequest(config, "DELETE", config.Project, deleteUrl, config.userAgent, nil) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error deleting for url %s : %s", deleteUrl, err) + } else { + log.Printf("[INFO][SWEEPER_LOG] Sent delete request for %s resource: %s", resourceName, name) + } + } + + if nonPrefixCount > 0 { + log.Printf("[INFO][SWEEPER_LOG] %d items were non-sweepable and skipped.", nonPrefixCount) + } + + return nil +} diff --git a/website/docs/r/cloudbuild_bitbucket_server_config.html.markdown b/website/docs/r/cloudbuild_bitbucket_server_config.html.markdown new file mode 100644 index 0000000000..5a9f582104 --- /dev/null +++ b/website/docs/r/cloudbuild_bitbucket_server_config.html.markdown @@ -0,0 +1,248 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** Type: MMv1 *** +# +# ---------------------------------------------------------------------------- +# +# 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. +# +# ---------------------------------------------------------------------------- +subcategory: "Cloud Build" +description: |- + BitbucketServerConfig represents the configuration for a Bitbucket Server. +--- + +# google\_cloudbuild\_bitbucket\_server\_config + +BitbucketServerConfig represents the configuration for a Bitbucket Server. + + +To get more information about BitbucketServerConfig, see: + +* [API documentation](https://cloud.google.com/build/docs/api/reference/rest/v1/projects.locations.bitbucketServerConfigs) +* How-to Guides + * [Connect to a Bitbucket Server host](https://cloud.google.com/build/docs/automating-builds/bitbucket/connect-host-bitbucket-server) + + +## Example Usage - Cloudbuild Bitbucket Server Config + + +```hcl +resource "google_cloudbuild_bitbucket_server_config" "bbs-config" { + config_id = "mybbsconfig" + location = "us-central1" + host_uri = "https://bbs.com" + secrets { + admin_access_token_version_name = "projects/myProject/secrets/mybbspat/versions/1" + read_access_token_version_name = "projects/myProject/secrets/mybbspat/versions/1" + webhook_secret_version_name = "projects/myProject/secrets/mybbspat/versions/1" + } + username = "test" + api_key = "" +} +``` +## Example Usage - Cloudbuild Bitbucket Server Config Repositories + + +```hcl +resource "google_cloudbuild_bitbucket_server_config" "bbs-config-with-repos" { + config_id = "mybbsconfig" + location = "us-central1" + host_uri = "https://bbs.com" + secrets { + admin_access_token_version_name = "projects/myProject/secrets/mybbspat/versions/1" + read_access_token_version_name = "projects/myProject/secrets/mybbspat/versions/1" + webhook_secret_version_name = "projects/myProject/secrets/mybbspat/versions/1" + } + username = "test" + api_key = "" + + connected_repositories { + project_key = "DEV" + repo_slug = "repo1" + } + connected_repositories { + project_key = "PROD" + repo_slug = "repo1" + } +} +``` + +## Example Usage - Cloudbuild Bitbucket Server Config Peered Network + + +```hcl +data "google_project" "project" {} + +resource "google_project_service" "servicenetworking" { + service = "servicenetworking.googleapis.com" + disable_on_destroy = false +} + +data "google_compute_network" "vpc_network" { + name = "vpc-network" + depends_on = [google_project_service.servicenetworking] +} + +resource "google_compute_global_address" "private_ip_alloc" { + name = "private-ip-alloc" + purpose = "VPC_PEERING" + address_type = "INTERNAL" + prefix_length = 16 + network = data.google_compute_network.vpc_network.id +} + +resource "google_service_networking_connection" "default" { + network = data.google_compute_network.vpc_network.id + service = "servicenetworking.googleapis.com" + reserved_peering_ranges = [google_compute_global_address.private_ip_alloc.name] + depends_on = [google_project_service.servicenetworking] +} + +resource "google_cloudbuild_bitbucket_server_config" "bbs-config-with-peered-network" { + config_id = "mybbsconfig" + location = "us-central1" + host_uri = "https://bbs.com" + secrets { + admin_access_token_version_name = "projects/myProject/secrets/mybbspat/versions/1" + read_access_token_version_name = "projects/myProject/secrets/mybbspat/versions/1" + webhook_secret_version_name = "projects/myProject/secrets/mybbspat/versions/1" + } + username = "test" + api_key = "" + peered_network = replace(data.google_compute_network.vpc_network.id, data.google_project.project.name, data.google_project.project.number) + ssl_ca = "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----\n" + depends_on = [google_service_networking_connection.default] +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `host_uri` - + (Required) + Immutable. The URI of the Bitbucket Server host. Once this field has been set, it cannot be changed. + If you need to change it, please create another BitbucketServerConfig. + +* `secrets` - + (Required) + Secret Manager secrets needed by the config. + Structure is [documented below](#nested_secrets). + +* `username` - + (Required) + Username of the account Cloud Build will use on Bitbucket Server. + +* `api_key` - + (Required) + Immutable. API Key that will be attached to webhook. Once this field has been set, it cannot be changed. + Changing this field will result in deleting/ recreating the resource. + +* `config_id` - + (Required) + The ID to use for the BitbucketServerConfig, which will become the final component of the BitbucketServerConfig's resource name. + +* `location` - + (Required) + The location of this bitbucket server config. + + +The `secrets` block supports: + +* `admin_access_token_version_name` - + (Required) + The resource name for the admin access token's secret version. + +* `read_access_token_version_name` - + (Required) + The resource name for the read access token's secret version. + +* `webhook_secret_version_name` - + (Required) + Immutable. The resource name for the webhook secret's secret version. Once this field has been set, it cannot be changed. + Changing this field will result in deleting/ recreating the resource. + +- - - + + +* `connected_repositories` - + (Optional) + Connected Bitbucket Server repositories for this config. + Structure is [documented below](#nested_connected_repositories). + +* `peered_network` - + (Optional) + The network to be used when reaching out to the Bitbucket Server instance. The VPC network must be enabled for private service connection. + This should be set if the Bitbucket Server instance is hosted on-premises and not reachable by public internet. If this field is left empty, + no network peering will occur and calls to the Bitbucket Server instance will be made over the public internet. Must be in the format + projects/{project}/global/networks/{network}, where {project} is a project number or id and {network} is the name of a VPC network in the project. + +* `ssl_ca` - + (Optional) + SSL certificate to use for requests to Bitbucket Server. The format should be PEM format but the extension can be one of .pem, .cer, or .crt. + +* `project` - (Optional) The ID of the project in which the resource belongs. + If it is not provided, the provider project is used. + + +The `connected_repositories` block supports: + +* `project_key` - + (Required) + Identifier for the project storing the repository. + +* `repo_slug` - + (Required) + Identifier for the repository. + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `id` - an identifier for the resource with format `projects/{{project}}/locations/{{location}}/bitbucketServerConfigs/{{config_id}}` + +* `name` - + The resource name for the config. + +* `webhook_key` - + Output only. UUID included in webhook requests. The UUID is used to look up the corresponding config. + + +## Timeouts + +This resource provides the following +[Timeouts](https://developer.hashicorp.com/terraform/plugin/sdkv2/resources/retries-and-customizable-timeouts) configuration options: + +- `create` - Default is 20 minutes. +- `update` - Default is 20 minutes. +- `delete` - Default is 20 minutes. + +## Import + + +BitbucketServerConfig can be imported using any of these accepted formats: + +``` +$ terraform import google_cloudbuild_bitbucket_server_config.default projects/{{project}}/locations/{{location}}/bitbucketServerConfigs/{{config_id}} +$ terraform import google_cloudbuild_bitbucket_server_config.default {{project}}/{{location}}/{{config_id}} +$ terraform import google_cloudbuild_bitbucket_server_config.default {{location}}/{{config_id}} +``` + +## User Project Overrides + +This resource supports [User Project Overrides](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/provider_reference#user_project_override).