From 197d35f45dd4e09153b5661de421e5547ee94481 Mon Sep 17 00:00:00 2001 From: Chris Stephens Date: Mon, 25 Nov 2019 21:03:00 +0000 Subject: [PATCH] Cloudrun v1 Signed-off-by: Modular Magician --- google/config.go | 3 + google/provider.go | 15 +- google/resource_cloud_run_domain_mapping.go | 792 +++++++ ...cloud_run_domain_mapping_generated_test.go | 111 + google/resource_cloud_run_service.go | 1965 +++++++++++++++++ google/resource_cloud_run_service_test.go | 153 +- google/resource_compute_backend_service.go | 6 - google/resource_compute_snapshot.go | 6 - google/transport.go | 25 +- google/transport_test.go | 19 + .../r/cloud_run_domain_mapping.html.markdown | 243 ++ .../docs/r/cloud_run_service.html.markdown | 492 +++++ 12 files changed, 3812 insertions(+), 18 deletions(-) create mode 100644 google/resource_cloud_run_domain_mapping.go create mode 100644 google/resource_cloud_run_domain_mapping_generated_test.go create mode 100644 google/resource_cloud_run_service.go create mode 100644 website/docs/r/cloud_run_domain_mapping.html.markdown create mode 100644 website/docs/r/cloud_run_service.html.markdown diff --git a/google/config.go b/google/config.go index 0200ff5a4c8..da68cc37ec0 100644 --- a/google/config.go +++ b/google/config.go @@ -78,6 +78,7 @@ type Config struct { BinaryAuthorizationBasePath string CloudBuildBasePath string CloudFunctionsBasePath string + CloudRunBasePath string CloudSchedulerBasePath string CloudTasksBasePath string ComputeBasePath string @@ -206,6 +207,7 @@ var BigtableDefaultBasePath = "https://bigtableadmin.googleapis.com/v2/" var BinaryAuthorizationDefaultBasePath = "https://binaryauthorization.googleapis.com/v1/" var CloudBuildDefaultBasePath = "https://cloudbuild.googleapis.com/v1/" var CloudFunctionsDefaultBasePath = "https://cloudfunctions.googleapis.com/v1/" +var CloudRunDefaultBasePath = "https://{{location}}-run.googleapis.com/apis/" var CloudSchedulerDefaultBasePath = "https://cloudscheduler.googleapis.com/v1/" var CloudTasksDefaultBasePath = "https://cloudtasks.googleapis.com/v2/" var ComputeDefaultBasePath = "https://www.googleapis.com/compute/v1/" @@ -673,6 +675,7 @@ func ConfigureBasePaths(c *Config) { c.BinaryAuthorizationBasePath = BinaryAuthorizationDefaultBasePath c.CloudBuildBasePath = CloudBuildDefaultBasePath c.CloudFunctionsBasePath = CloudFunctionsDefaultBasePath + c.CloudRunBasePath = CloudRunDefaultBasePath c.CloudSchedulerBasePath = CloudSchedulerDefaultBasePath c.CloudTasksBasePath = CloudTasksDefaultBasePath c.ComputeBasePath = ComputeDefaultBasePath diff --git a/google/provider.go b/google/provider.go index 0c2511d485e..cdd6251a436 100644 --- a/google/provider.go +++ b/google/provider.go @@ -167,6 +167,14 @@ func Provider() terraform.ResourceProvider { "GOOGLE_CLOUD_FUNCTIONS_CUSTOM_ENDPOINT", }, CloudFunctionsDefaultBasePath), }, + "cloud_run_custom_endpoint": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateCustomEndpoint, + DefaultFunc: schema.MultiEnvDefaultFunc([]string{ + "GOOGLE_CLOUD_RUN_CUSTOM_ENDPOINT", + }, CloudRunDefaultBasePath), + }, "cloud_scheduler_custom_endpoint": { Type: schema.TypeString, Optional: true, @@ -445,9 +453,9 @@ func Provider() terraform.ResourceProvider { return provider } -// Generated resources: 83 +// Generated resources: 85 // Generated IAM resources: 39 -// Total generated resources: 122 +// Total generated resources: 124 func ResourceMap() map[string]*schema.Resource { resourceMap, _ := ResourceMapWithErrors() return resourceMap @@ -475,6 +483,8 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { "google_cloudfunctions_function_iam_binding": ResourceIamBinding(CloudFunctionsCloudFunctionIamSchema, CloudFunctionsCloudFunctionIamUpdaterProducer, CloudFunctionsCloudFunctionIdParseFunc), "google_cloudfunctions_function_iam_member": ResourceIamMember(CloudFunctionsCloudFunctionIamSchema, CloudFunctionsCloudFunctionIamUpdaterProducer, CloudFunctionsCloudFunctionIdParseFunc), "google_cloudfunctions_function_iam_policy": ResourceIamPolicy(CloudFunctionsCloudFunctionIamSchema, CloudFunctionsCloudFunctionIamUpdaterProducer, CloudFunctionsCloudFunctionIdParseFunc), + "google_cloud_run_domain_mapping": resourceCloudRunDomainMapping(), + "google_cloud_run_service": resourceCloudRunService(), "google_cloud_scheduler_job": resourceCloudSchedulerJob(), "google_cloud_tasks_queue": resourceCloudTasksQueue(), "google_compute_address": resourceComputeAddress(), @@ -732,6 +742,7 @@ func providerConfigure(d *schema.ResourceData, terraformVersion string) (interfa config.BinaryAuthorizationBasePath = d.Get("binary_authorization_custom_endpoint").(string) config.CloudBuildBasePath = d.Get("cloud_build_custom_endpoint").(string) config.CloudFunctionsBasePath = d.Get("cloud_functions_custom_endpoint").(string) + config.CloudRunBasePath = d.Get("cloud_run_custom_endpoint").(string) config.CloudSchedulerBasePath = d.Get("cloud_scheduler_custom_endpoint").(string) config.CloudTasksBasePath = d.Get("cloud_tasks_custom_endpoint").(string) config.ComputeBasePath = d.Get("compute_custom_endpoint").(string) diff --git a/google/resource_cloud_run_domain_mapping.go b/google/resource_cloud_run_domain_mapping.go new file mode 100644 index 00000000000..0e207a98fe3 --- /dev/null +++ b/google/resource_cloud_run_domain_mapping.go @@ -0,0 +1,792 @@ +// ---------------------------------------------------------------------------- +// +// *** 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" + "strconv" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func resourceCloudRunDomainMapping() *schema.Resource { + return &schema.Resource{ + Create: resourceCloudRunDomainMappingCreate, + Read: resourceCloudRunDomainMappingRead, + Update: resourceCloudRunDomainMappingUpdate, + Delete: resourceCloudRunDomainMappingDelete, + + Importer: &schema.ResourceImporter{ + State: resourceCloudRunDomainMappingImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(4 * time.Minute), + Update: schema.DefaultTimeout(4 * time.Minute), + Delete: schema.DefaultTimeout(4 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "location": { + Type: schema.TypeString, + Required: true, + Description: `The location of the cloud run instance. eg us-central1`, + }, + "metadata": { + Type: schema.TypeList, + Required: true, + Description: `Metadata associated with this DomainMapping.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "namespace": { + Type: schema.TypeString, + Required: true, + Description: `In Cloud Run the namespace must be equal to either the +project ID or project number.`, + }, + "annotations": { + Type: schema.TypeMap, + Computed: true, + Optional: true, + Description: `Annotations is a key value map stored with a resource that +may be set by external tools to store and retrieve arbitrary metadata. More +info: http://kubernetes.io/docs/user-guide/annotations`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "labels": { + Type: schema.TypeMap, + Computed: true, + Optional: true, + Description: `Map of string keys and values that can be used to organize and categorize +(scope and select) objects. May match selectors of replication controllers +and routes. +More info: http://kubernetes.io/docs/user-guide/labels`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "generation": { + Type: schema.TypeInt, + Computed: true, + Description: `A sequence number representing a specific generation of the desired state.`, + }, + "resource_version": { + Type: schema.TypeString, + Computed: true, + Description: `An opaque value that represents the internal version of this object that +can be used by clients to determine when objects have changed. May be used +for optimistic concurrency, change detection, and the watch operation on a +resource or set of resources. They may only be valid for a +particular resource or set of resources. + +More info: +https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency`, + }, + "self_link": { + Type: schema.TypeString, + Computed: true, + Description: `SelfLink is a URL representing this object.`, + }, + "uid": { + Type: schema.TypeString, + Computed: true, + Description: `UID is a unique id generated by the server on successful creation of a resource and is not +allowed to change on PUT operations. + +More info: http://kubernetes.io/docs/user-guide/identifiers#uids`, + }, + }, + }, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `Name should be a verified domain`, + }, + "spec": { + Type: schema.TypeList, + Required: true, + Description: `The spec for this DomainMapping.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "route_name": { + Type: schema.TypeString, + Required: true, + DiffSuppressFunc: compareSelfLinkOrResourceName, + Description: `The name of the Cloud Run Service that this DomainMapping applies to. +The route must exist.`, + }, + "certificate_mode": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"NONE", "AUTOMATIC", ""}, false), + Description: `The mode of the certificate.`, + Default: "AUTOMATIC", + }, + "force_override": { + Type: schema.TypeBool, + Optional: true, + Description: `If set, the mapping will override any mapping set before this spec was set. +It is recommended that the user leaves this empty to receive an error +warning about a potential conflict and only set it once the respective UI +has given such a warning.`, + }, + }, + }, + }, + "status": { + Type: schema.TypeList, + Computed: true, + Description: `The current status of the DomainMapping.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "resource_records": { + Type: schema.TypeList, + Optional: true, + Description: `The resource records required to configure this domain mapping. These +records must be added to the domain's DNS configuration in order to +serve the application via this domain mapping.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"A", "AAAA", "CNAME", ""}, false), + Description: `Resource record type. Example: 'AAAA'.`, + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: `Relative name of the object affected by this record. Only applicable for +'CNAME' records. Example: 'www'.`, + }, + "rrdata": { + Type: schema.TypeString, + Computed: true, + Description: `Data for this record. Values vary by record type, as defined in RFC 1035 +(section 5) and RFC 1034 (section 3.6.1).`, + }, + }, + }, + }, + "conditions": { + Type: schema.TypeList, + Computed: true, + Description: `Array of observed DomainMappingConditions, indicating the current state +of the DomainMapping.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "message": { + Type: schema.TypeString, + Computed: true, + Description: `Human readable message indicating details about the current status.`, + }, + "reason": { + Type: schema.TypeString, + Computed: true, + Description: `One-word CamelCase reason for the condition's current status.`, + }, + "status": { + Type: schema.TypeString, + Computed: true, + Description: `Status of the condition, one of True, False, Unknown.`, + }, + "type": { + Type: schema.TypeString, + Computed: true, + Description: `Type of domain mapping condition.`, + }, + }, + }, + }, + "mapped_route_name": { + Type: schema.TypeString, + Computed: true, + Description: `The name of the route that the mapping currently points to.`, + }, + "observed_generation": { + Type: schema.TypeInt, + Computed: true, + Description: `ObservedGeneration is the 'Generation' of the DomainMapping that +was last processed by the controller.`, + }, + }, + }, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + } +} + +func resourceCloudRunDomainMappingCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := make(map[string]interface{}) + specProp, err := expandCloudRunDomainMappingSpec(d.Get("spec"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("spec"); !isEmptyValue(reflect.ValueOf(specProp)) && (ok || !reflect.DeepEqual(v, specProp)) { + obj["spec"] = specProp + } + metadataProp, err := expandCloudRunDomainMappingMetadata(d.Get("metadata"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("metadata"); !isEmptyValue(reflect.ValueOf(metadataProp)) && (ok || !reflect.DeepEqual(v, metadataProp)) { + obj["metadata"] = metadataProp + } + + obj, err = resourceCloudRunDomainMappingEncoder(d, meta, obj) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{CloudRunBasePath}}domains.cloudrun.com/v1/namespaces/{{project}}/domainmappings") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new DomainMapping: %#v", obj) + project, err := getProject(d, config) + if err != nil { + return err + } + res, err := sendRequestWithTimeout(config, "POST", project, url, obj, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return fmt.Errorf("Error creating DomainMapping: %s", err) + } + + // Store the ID now + id, err := replaceVars(d, config, "locations/{{location}}/namespaces/{{project}}/domainmappings/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating DomainMapping %q: %#v", d.Id(), res) + + return resourceCloudRunDomainMappingRead(d, meta) +} + +func resourceCloudRunDomainMappingRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + url, err := replaceVars(d, config, "{{CloudRunBasePath}}domains.cloudrun.com/v1/namespaces/{{project}}/domainmappings/{{name}}") + if err != nil { + return err + } + + project, err := getProject(d, config) + if err != nil { + return err + } + res, err := sendRequest(config, "GET", project, url, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("CloudRunDomainMapping %q", d.Id())) + } + + res, err = resourceCloudRunDomainMappingDecoder(d, meta, res) + if err != nil { + return err + } + + if res == nil { + // Decoding the object has resulted in it being gone. It may be marked deleted + log.Printf("[DEBUG] Removing CloudRunDomainMapping because it no longer exists.") + d.SetId("") + return nil + } + + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error reading DomainMapping: %s", err) + } + + if err := d.Set("status", flattenCloudRunDomainMappingStatus(res["status"], d)); err != nil { + return fmt.Errorf("Error reading DomainMapping: %s", err) + } + if err := d.Set("spec", flattenCloudRunDomainMappingSpec(res["spec"], d)); err != nil { + return fmt.Errorf("Error reading DomainMapping: %s", err) + } + if err := d.Set("metadata", flattenCloudRunDomainMappingMetadata(res["metadata"], d)); err != nil { + return fmt.Errorf("Error reading DomainMapping: %s", err) + } + + return nil +} + +func resourceCloudRunDomainMappingUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + obj := make(map[string]interface{}) + specProp, err := expandCloudRunDomainMappingSpec(d.Get("spec"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("spec"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, specProp)) { + obj["spec"] = specProp + } + metadataProp, err := expandCloudRunDomainMappingMetadata(d.Get("metadata"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("metadata"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, metadataProp)) { + obj["metadata"] = metadataProp + } + + obj, err = resourceCloudRunDomainMappingEncoder(d, meta, obj) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{CloudRunBasePath}}domains.cloudrun.com/v1/namespaces/{{project}}/domainmappings/{{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating DomainMapping %q: %#v", d.Id(), obj) + _, err = sendRequestWithTimeout(config, "PUT", project, url, obj, d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return fmt.Errorf("Error updating DomainMapping %q: %s", d.Id(), err) + } + + return resourceCloudRunDomainMappingRead(d, meta) +} + +func resourceCloudRunDomainMappingDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{CloudRunBasePath}}domains.cloudrun.com/v1/namespaces/{{project}}/domainmappings/{{name}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting DomainMapping %q", d.Id()) + + res, err := sendRequestWithTimeout(config, "DELETE", project, url, obj, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return handleNotFoundError(err, d, "DomainMapping") + } + + log.Printf("[DEBUG] Finished deleting DomainMapping %q: %#v", d.Id(), res) + return nil +} + +func resourceCloudRunDomainMappingImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + if err := parseImportId([]string{ + "locations/(?P[^/]+)/namespaces/(?P[^/]+)/domainmappings/(?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, "locations/{{location}}/namespaces/{{project}}/domainmappings/{{name}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenCloudRunDomainMappingStatus(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["conditions"] = + flattenCloudRunDomainMappingStatusConditions(original["conditions"], d) + transformed["observed_generation"] = + flattenCloudRunDomainMappingStatusObservedGeneration(original["observedGeneration"], d) + transformed["resource_records"] = + flattenCloudRunDomainMappingStatusResourceRecords(original["resourceRecords"], d) + transformed["mapped_route_name"] = + flattenCloudRunDomainMappingStatusMappedRouteName(original["mappedRouteName"], d) + return []interface{}{transformed} +} +func flattenCloudRunDomainMappingStatusConditions(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{}{ + "message": flattenCloudRunDomainMappingStatusConditionsMessage(original["message"], d), + "status": flattenCloudRunDomainMappingStatusConditionsStatus(original["status"], d), + "reason": flattenCloudRunDomainMappingStatusConditionsReason(original["reason"], d), + "type": flattenCloudRunDomainMappingStatusConditionsType(original["type"], d), + }) + } + return transformed +} +func flattenCloudRunDomainMappingStatusConditionsMessage(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunDomainMappingStatusConditionsStatus(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunDomainMappingStatusConditionsReason(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunDomainMappingStatusConditionsType(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunDomainMappingStatusObservedGeneration(v interface{}, d *schema.ResourceData) interface{} { + // Handles the string fixed64 format + if strVal, ok := v.(string); ok { + if intVal, err := strconv.ParseInt(strVal, 10, 64); err == nil { + return intVal + } // let terraform core handle it if we can't convert the string to an int. + } + return v +} + +func flattenCloudRunDomainMappingStatusResourceRecords(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{}{ + "type": flattenCloudRunDomainMappingStatusResourceRecordsType(original["type"], d), + "rrdata": flattenCloudRunDomainMappingStatusResourceRecordsRrdata(original["rrdata"], d), + "name": flattenCloudRunDomainMappingStatusResourceRecordsName(original["name"], d), + }) + } + return transformed +} +func flattenCloudRunDomainMappingStatusResourceRecordsType(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunDomainMappingStatusResourceRecordsRrdata(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunDomainMappingStatusResourceRecordsName(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunDomainMappingStatusMappedRouteName(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunDomainMappingSpec(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["force_override"] = + flattenCloudRunDomainMappingSpecForceOverride(original["forceOverride"], d) + transformed["route_name"] = + flattenCloudRunDomainMappingSpecRouteName(original["routeName"], d) + transformed["certificate_mode"] = + flattenCloudRunDomainMappingSpecCertificateMode(original["certificateMode"], d) + return []interface{}{transformed} +} +func flattenCloudRunDomainMappingSpecForceOverride(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunDomainMappingSpecRouteName(v interface{}, d *schema.ResourceData) interface{} { + if v == nil { + return v + } + return ConvertSelfLinkToV1(v.(string)) +} + +func flattenCloudRunDomainMappingSpecCertificateMode(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunDomainMappingMetadata(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["labels"] = + flattenCloudRunDomainMappingMetadataLabels(original["labels"], d) + transformed["generation"] = + flattenCloudRunDomainMappingMetadataGeneration(original["generation"], d) + transformed["resource_version"] = + flattenCloudRunDomainMappingMetadataResourceVersion(original["resourceVersion"], d) + transformed["self_link"] = + flattenCloudRunDomainMappingMetadataSelfLink(original["selfLink"], d) + transformed["uid"] = + flattenCloudRunDomainMappingMetadataUid(original["uid"], d) + transformed["namespace"] = + flattenCloudRunDomainMappingMetadataNamespace(original["namespace"], d) + transformed["annotations"] = + flattenCloudRunDomainMappingMetadataAnnotations(original["annotations"], d) + return []interface{}{transformed} +} +func flattenCloudRunDomainMappingMetadataLabels(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunDomainMappingMetadataGeneration(v interface{}, d *schema.ResourceData) interface{} { + // Handles the string fixed64 format + if strVal, ok := v.(string); ok { + if intVal, err := strconv.ParseInt(strVal, 10, 64); err == nil { + return intVal + } // let terraform core handle it if we can't convert the string to an int. + } + return v +} + +func flattenCloudRunDomainMappingMetadataResourceVersion(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunDomainMappingMetadataSelfLink(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunDomainMappingMetadataUid(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunDomainMappingMetadataNamespace(v interface{}, d *schema.ResourceData) interface{} { + return d.Get("project") +} + +func flattenCloudRunDomainMappingMetadataAnnotations(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func expandCloudRunDomainMappingSpec(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{}) + + transformedForceOverride, err := expandCloudRunDomainMappingSpecForceOverride(original["force_override"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedForceOverride); val.IsValid() && !isEmptyValue(val) { + transformed["forceOverride"] = transformedForceOverride + } + + transformedRouteName, err := expandCloudRunDomainMappingSpecRouteName(original["route_name"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedRouteName); val.IsValid() && !isEmptyValue(val) { + transformed["routeName"] = transformedRouteName + } + + transformedCertificateMode, err := expandCloudRunDomainMappingSpecCertificateMode(original["certificate_mode"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedCertificateMode); val.IsValid() && !isEmptyValue(val) { + transformed["certificateMode"] = transformedCertificateMode + } + + return transformed, nil +} + +func expandCloudRunDomainMappingSpecForceOverride(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunDomainMappingSpecRouteName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + f, err := parseGlobalFieldValue("services", v.(string), "project", d, config, true) + if err != nil { + return nil, fmt.Errorf("Invalid value for route_name: %s", err) + } + return f.RelativeLink(), nil +} + +func expandCloudRunDomainMappingSpecCertificateMode(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunDomainMappingMetadata(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{}) + + transformedLabels, err := expandCloudRunDomainMappingMetadataLabels(original["labels"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedLabels); val.IsValid() && !isEmptyValue(val) { + transformed["labels"] = transformedLabels + } + + transformedGeneration, err := expandCloudRunDomainMappingMetadataGeneration(original["generation"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedGeneration); val.IsValid() && !isEmptyValue(val) { + transformed["generation"] = transformedGeneration + } + + transformedResourceVersion, err := expandCloudRunDomainMappingMetadataResourceVersion(original["resource_version"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedResourceVersion); val.IsValid() && !isEmptyValue(val) { + transformed["resourceVersion"] = transformedResourceVersion + } + + transformedSelfLink, err := expandCloudRunDomainMappingMetadataSelfLink(original["self_link"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedSelfLink); val.IsValid() && !isEmptyValue(val) { + transformed["selfLink"] = transformedSelfLink + } + + transformedUid, err := expandCloudRunDomainMappingMetadataUid(original["uid"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedUid); val.IsValid() && !isEmptyValue(val) { + transformed["uid"] = transformedUid + } + + transformedNamespace, err := expandCloudRunDomainMappingMetadataNamespace(original["namespace"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedNamespace); val.IsValid() && !isEmptyValue(val) { + transformed["namespace"] = transformedNamespace + } + + transformedAnnotations, err := expandCloudRunDomainMappingMetadataAnnotations(original["annotations"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedAnnotations); val.IsValid() && !isEmptyValue(val) { + transformed["annotations"] = transformedAnnotations + } + + return transformed, nil +} + +func expandCloudRunDomainMappingMetadataLabels(v interface{}, d TerraformResourceData, 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 expandCloudRunDomainMappingMetadataGeneration(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunDomainMappingMetadataResourceVersion(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunDomainMappingMetadataSelfLink(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunDomainMappingMetadataUid(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunDomainMappingMetadataNamespace(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunDomainMappingMetadataAnnotations(v interface{}, d TerraformResourceData, 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 resourceCloudRunDomainMappingEncoder(d *schema.ResourceData, meta interface{}, obj map[string]interface{}) (map[string]interface{}, error) { + name := d.Get("name").(string) + metadata := obj["metadata"].(map[string]interface{}) + metadata["name"] = name + + // The only acceptable version/kind right now + obj["apiVersion"] = "domains.cloudrun.com/v1" + obj["kind"] = "DomainMapping" + return obj, nil +} + +func resourceCloudRunDomainMappingDecoder(d *schema.ResourceData, meta interface{}, res map[string]interface{}) (map[string]interface{}, error) { + // metadata is not present if the API returns an error + if obj, ok := res["metadata"]; ok { + if meta, ok := obj.(map[string]interface{}); ok { + res["name"] = meta["name"] + } else { + return nil, fmt.Errorf("Unable to decode 'metadata' block from API response.") + } + } + return res, nil +} diff --git a/google/resource_cloud_run_domain_mapping_generated_test.go b/google/resource_cloud_run_domain_mapping_generated_test.go new file mode 100644 index 00000000000..be989ec1446 --- /dev/null +++ b/google/resource_cloud_run_domain_mapping_generated_test.go @@ -0,0 +1,111 @@ +// ---------------------------------------------------------------------------- +// +// *** 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-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccCloudRunDomainMapping_cloudRunDomainMappingBasicExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "namespace": getTestProjectFromEnv(), + "verified_domain": "tftest-domainmapping.com", + "random_suffix": acctest.RandString(10), + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudRunDomainMappingDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudRunDomainMapping_cloudRunDomainMappingBasicExample(context), + }, + { + ResourceName: "google_cloud_run_domain_mapping.default", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCloudRunDomainMapping_cloudRunDomainMappingBasicExample(context map[string]interface{}) string { + return Nprintf(` + +resource "google_cloud_run_service" "default" { + name = "tftest-cloudrun%{random_suffix}" + location = "us-central1" + + metadata { + namespace = "%{namespace}" + } + + template { + spec { + containers { + image = "gcr.io/cloudrun/hello" + } + } + } +} + +resource "google_cloud_run_domain_mapping" "default" { + location = "us-central1" + name = "%{verified_domain}" + + metadata { + namespace = "%{namespace}" + } + + spec { + route_name = google_cloud_run_service.default.name + } +} +`, context) +} + +func testAccCheckCloudRunDomainMappingDestroy(s *terraform.State) error { + for name, rs := range s.RootModule().Resources { + if rs.Type != "google_cloud_run_domain_mapping" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := testAccProvider.Meta().(*Config) + + url, err := replaceVarsForTest(config, rs, "{{CloudRunBasePath}}domains.cloudrun.com/v1/namespaces/{{project}}/domainmappings/{{name}}") + if err != nil { + return err + } + + _, err = sendRequest(config, "GET", "", url, nil) + if err == nil { + return fmt.Errorf("CloudRunDomainMapping still exists at %s", url) + } + } + + return nil +} diff --git a/google/resource_cloud_run_service.go b/google/resource_cloud_run_service.go new file mode 100644 index 00000000000..ce5934d1fee --- /dev/null +++ b/google/resource_cloud_run_service.go @@ -0,0 +1,1965 @@ +// ---------------------------------------------------------------------------- +// +// *** 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" + "strconv" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func resourceCloudRunService() *schema.Resource { + return &schema.Resource{ + Create: resourceCloudRunServiceCreate, + Read: resourceCloudRunServiceRead, + Update: resourceCloudRunServiceUpdate, + Delete: resourceCloudRunServiceDelete, + + Importer: &schema.ResourceImporter{ + State: resourceCloudRunServiceImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(4 * time.Minute), + Update: schema.DefaultTimeout(4 * time.Minute), + Delete: schema.DefaultTimeout(4 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "location": { + Type: schema.TypeString, + Required: true, + Description: `The location of the cloud run instance. eg us-central1`, + }, + "metadata": { + Type: schema.TypeList, + Required: true, + Description: `Metadata associated with this Service, including name, namespace, labels, +and annotations.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "annotations": { + Type: schema.TypeMap, + Computed: true, + Optional: true, + Description: `Annotations is a key value map stored with a resource that +may be set by external tools to store and retrieve arbitrary metadata. More +info: http://kubernetes.io/docs/user-guide/annotations`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "labels": { + Type: schema.TypeMap, + Computed: true, + Optional: true, + Description: `Map of string keys and values that can be used to organize and categorize +(scope and select) objects. May match selectors of replication controllers +and routes. +More info: http://kubernetes.io/docs/user-guide/labels`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "namespace": { + Type: schema.TypeString, + Computed: true, + Optional: true, + Description: `In Cloud Run the namespace must be equal to either the +project ID or project number.`, + }, + "generation": { + Type: schema.TypeInt, + Computed: true, + Description: `A sequence number representing a specific generation of the desired state.`, + }, + "resource_version": { + Type: schema.TypeString, + Computed: true, + Description: `An opaque value that represents the internal version of this object that +can be used by clients to determine when objects have changed. May be used +for optimistic concurrency, change detection, and the watch operation on a +resource or set of resources. They may only be valid for a +particular resource or set of resources. + +More info: +https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency`, + }, + "self_link": { + Type: schema.TypeString, + Computed: true, + Description: `SelfLink is a URL representing this object.`, + }, + "uid": { + Type: schema.TypeString, + Computed: true, + Description: `UID is a unique id generated by the server on successful creation of a resource and is not +allowed to change on PUT operations. + +More info: http://kubernetes.io/docs/user-guide/identifiers#uids`, + }, + }, + }, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `Name must be unique within a namespace, within a Cloud Run region. +Is required when creating resources. Name is primarily intended +for creation idempotence and configuration definition. Cannot be updated. +More info: http://kubernetes.io/docs/user-guide/identifiers#names`, + }, + "template": { + Type: schema.TypeList, + Optional: true, + Description: `template holds the latest specification for the Revision to +be stamped out. The template references the container image, and may also +include labels and annotations that should be attached to the Revision. +To correlate a Revision, and/or to force a Revision to be created when the +spec doesn't otherwise change, a nonce label may be provided in the +template metadata. For more details, see: +https://github.com/knative/serving/blob/master/docs/client-conventions.md#associate-modifications-with-revisions + +Cloud Run does not currently support referencing a build that is +responsible for materializing the container image from source.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "spec": { + Type: schema.TypeList, + Required: true, + Description: `RevisionSpec holds the desired state of the Revision (from the client).`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "containers": { + Type: schema.TypeList, + Required: true, + Description: `Container defines the unit of execution for this Revision. +In the context of a Revision, we disallow a number of the fields of +this Container, including: name, ports, and volumeMounts. +The runtime contract is documented here: +https://github.com/knative/serving/blob/master/docs/runtime-contract.md`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "image": { + Type: schema.TypeString, + Required: true, + Description: `Docker image name. This is most often a reference to a container located +in the container registry, such as gcr.io/cloudrun/hello +More info: https://kubernetes.io/docs/concepts/containers/images`, + }, + "args": { + Type: schema.TypeList, + Optional: true, + Description: `Arguments to the entrypoint. +The docker image's CMD is used if this is not provided. +Variable references $(VAR_NAME) are expanded using the container's +environment. If a variable cannot be resolved, the reference in the input +string will be unchanged. The $(VAR_NAME) syntax can be escaped with a +double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, +regardless of whether the variable exists or not. +More info: +https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell`, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "command": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Description: `Entrypoint array. Not executed within a shell. +The docker image's ENTRYPOINT is used if this is not provided. +Variable references $(VAR_NAME) are expanded using the container's +environment. If a variable cannot be resolved, the reference in the input +string will be unchanged. The $(VAR_NAME) syntax can be escaped with a +double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, +regardless of whether the variable exists or not. +More info: +https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell`, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "env": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Description: `List of environment variables to set in the container.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + Description: `Name of the environment variable.`, + }, + "value": { + Type: schema.TypeString, + Optional: true, + Description: `Variable references $(VAR_NAME) are expanded +using the previous defined environment variables in the container and +any route environment variables. If a variable cannot be resolved, +the reference in the input string will be unchanged. The $(VAR_NAME) +syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped +references will never be expanded, regardless of whether the variable +exists or not. +Defaults to "".`, + }, + }, + }, + }, + "env_from": { + Type: schema.TypeList, + Optional: true, + Deprecated: "Not supported by Cloud Run fully managed", + ForceNew: true, + Description: `List of sources to populate environment variables in the container. +All invalid keys will be reported as an event when the container is starting. +When a key exists in multiple sources, the value associated with the last source will +take precedence. Values defined by an Env with a duplicate key will take +precedence.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "config_map_ref": { + Type: schema.TypeList, + Optional: true, + Description: `The ConfigMap to select from.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "local_object_reference": { + Type: schema.TypeList, + Optional: true, + Description: `The ConfigMap to select from.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: `Name of the referent. +More info: +https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names`, + }, + }, + }, + }, + "optional": { + Type: schema.TypeBool, + Optional: true, + Description: `Specify whether the ConfigMap must be defined`, + }, + }, + }, + }, + "prefix": { + Type: schema.TypeString, + Optional: true, + Description: `An optional identifier to prepend to each key in the ConfigMap.`, + }, + "secret_ref": { + Type: schema.TypeList, + Optional: true, + Description: `The Secret to select from.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "local_object_reference": { + Type: schema.TypeList, + Optional: true, + Description: `The Secret to select from.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: `Name of the referent. +More info: +https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names`, + }, + }, + }, + }, + "optional": { + Type: schema.TypeBool, + Optional: true, + Description: `Specify whether the Secret must be defined`, + }, + }, + }, + }, + }, + }, + }, + "resources": { + Type: schema.TypeList, + Optional: true, + Description: `Compute Resources required by this container. Used to set values such as max memory +More info: +https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "limits": { + Type: schema.TypeMap, + Optional: true, + Description: `Limits describes the maximum amount of compute resources allowed. +The values of the map is string form of the 'quantity' k8s type: +https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery/pkg/api/resource/quantity.go`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "requests": { + Type: schema.TypeMap, + Optional: true, + Description: `Requests describes the minimum amount of compute resources required. +If Requests is omitted for a container, it defaults to Limits if that is +explicitly specified, otherwise to an implementation-defined value. +The values of the map is string form of the 'quantity' k8s type: +https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery/pkg/api/resource/quantity.go`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + "working_dir": { + Type: schema.TypeString, + Optional: true, + Deprecated: "Not supported by Cloud Run fully managed", + ForceNew: true, + Description: `Container's working directory. +If not specified, the container runtime's default will be used, which +might be configured in the container image.`, + }, + }, + }, + }, + "container_concurrency": { + Type: schema.TypeInt, + Optional: true, + Description: `ContainerConcurrency specifies the maximum allowed in-flight (concurrent) +requests per container of the Revision. Values are: +- '0' thread-safe, the system should manage the max concurrency. This is + the default value. +- '1' not-thread-safe. Single concurrency +- '2-N' thread-safe, max concurrency of N`, + }, + "service_account_name": { + Type: schema.TypeString, + Optional: true, + Description: `Email address of the IAM service account associated with the revision of the +service. The service account represents the identity of the running revision, +and determines what permissions the revision has. If not provided, the revision +will use the project's default service account.`, + }, + "serving_state": { + Type: schema.TypeString, + Computed: true, + Deprecated: "Not supported by Cloud Run fully managed", + Description: `ServingState holds a value describing the state the resources +are in for this Revision. +It is expected +that the system will manipulate this based on routability and load.`, + }, + }, + }, + }, + "metadata": { + Type: schema.TypeList, + Optional: true, + Description: `Optional metadata for this Revision, including labels and annotations. +Name will be generated by the Configuration. To set minimum instances +for this revision, use the "autoscaling.knative.dev/minScale" annotation +key. To set maximum instances for this revision, use the +"autoscaling.knative.dev/maxScale" annotation key. To set Cloud SQL +connections for the revision, use the "run.googleapis.com/cloudsql-instances" +annotation key.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "annotations": { + Type: schema.TypeMap, + Optional: true, + Description: `Annotations is a key value map stored with a resource that +may be set by external tools to store and retrieve arbitrary metadata. More +info: http://kubernetes.io/docs/user-guide/annotations`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "labels": { + Type: schema.TypeMap, + Optional: true, + Description: `Map of string keys and values that can be used to organize and categorize +(scope and select) objects. May match selectors of replication controllers +and routes. +More info: http://kubernetes.io/docs/user-guide/labels`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "name": { + Type: schema.TypeString, + Computed: true, + Optional: true, + Description: `Name must be unique within a namespace, within a Cloud Run region. +Is required when creating resources. Name is primarily intended +for creation idempotence and configuration definition. Cannot be updated. +More info: http://kubernetes.io/docs/user-guide/identifiers#names`, + }, + "namespace": { + Type: schema.TypeString, + Computed: true, + Optional: true, + Description: `In Cloud Run the namespace must be equal to either the +project ID or project number.`, + }, + "generation": { + Type: schema.TypeInt, + Computed: true, + Description: `A sequence number representing a specific generation of the desired state.`, + }, + "resource_version": { + Type: schema.TypeString, + Computed: true, + Description: `An opaque value that represents the internal version of this object that +can be used by clients to determine when objects have changed. May be used +for optimistic concurrency, change detection, and the watch operation on a +resource or set of resources. They may only be valid for a +particular resource or set of resources. + +More info: +https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency`, + }, + "self_link": { + Type: schema.TypeString, + Computed: true, + Description: `SelfLink is a URL representing this object.`, + }, + "uid": { + Type: schema.TypeString, + Computed: true, + Description: `UID is a unique id generated by the server on successful creation of a resource and is not +allowed to change on PUT operations. + +More info: http://kubernetes.io/docs/user-guide/identifiers#uids`, + }, + }, + }, + }, + }, + }, + }, + "traffic": { + Type: schema.TypeList, + Computed: true, + Optional: true, + Description: `Traffic specifies how to distribute traffic over a collection of Knative Revisions +and Configurations`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "percent": { + Type: schema.TypeInt, + Required: true, + Description: `Percent specifies percent of the traffic to this Revision or Configuration.`, + }, + "latest_revision": { + Type: schema.TypeBool, + Optional: true, + Description: `LatestRevision may be optionally provided to indicate that the latest ready +Revision of the Configuration should be used for this traffic target. When +provided LatestRevision must be true if RevisionName is empty; it must be +false when RevisionName is non-empty.`, + }, + "revision_name": { + Type: schema.TypeString, + Optional: true, + Description: `RevisionName of a specific revision to which to send this portion of traffic.`, + }, + }, + }, + }, + + "status": { + Type: schema.TypeList, + Computed: true, + Description: `The current status of the Service.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "conditions": { + Type: schema.TypeList, + Computed: true, + Description: `Array of observed Service Conditions, indicating the current ready state of the service.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "message": { + Type: schema.TypeString, + Computed: true, + Description: `Human readable message indicating details about the current status.`, + }, + "reason": { + Type: schema.TypeString, + Computed: true, + Description: `One-word CamelCase reason for the condition's current status.`, + }, + "status": { + Type: schema.TypeString, + Computed: true, + Description: `Status of the condition, one of True, False, Unknown.`, + }, + "type": { + Type: schema.TypeString, + Computed: true, + Description: `Type of domain mapping condition.`, + }, + }, + }, + }, + "latest_created_revision_name": { + Type: schema.TypeString, + Computed: true, + Description: `From ConfigurationStatus. LatestCreatedRevisionName is the last revision that was created +from this Service's Configuration. It might not be ready yet, for that use +LatestReadyRevisionName.`, + }, + "latest_ready_revision_name": { + Type: schema.TypeString, + Computed: true, + Description: `From ConfigurationStatus. LatestReadyRevisionName holds the name of the latest Revision +stamped out from this Service's Configuration that has had its "Ready" condition become +"True".`, + }, + "observed_generation": { + Type: schema.TypeInt, + Computed: true, + Description: `ObservedGeneration is the 'Generation' of the Route that was last processed by the +controller. + +Clients polling for completed reconciliation should poll until observedGeneration = +metadata.generation and the Ready condition's status is True or False.`, + }, + "url": { + Type: schema.TypeString, + Computed: true, + Description: `From RouteStatus. URL holds the url that will distribute traffic over the provided traffic +targets. It generally has the form +https://{route-hash}-{project-hash}-{cluster-level-suffix}.a.run.app`, + }, + }, + }, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + } +} + +func resourceCloudRunServiceCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := make(map[string]interface{}) + specProp, err := expandCloudRunServiceSpec(nil, d, config) + if err != nil { + return err + } else if !isEmptyValue(reflect.ValueOf(specProp)) { + obj["spec"] = specProp + } + metadataProp, err := expandCloudRunServiceMetadata(d.Get("metadata"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("metadata"); !isEmptyValue(reflect.ValueOf(metadataProp)) && (ok || !reflect.DeepEqual(v, metadataProp)) { + obj["metadata"] = metadataProp + } + + obj, err = resourceCloudRunServiceEncoder(d, meta, obj) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{CloudRunBasePath}}serving.knative.dev/v1/namespaces/{{project}}/services") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new Service: %#v", obj) + project, err := getProject(d, config) + if err != nil { + return err + } + res, err := sendRequestWithTimeout(config, "POST", project, url, obj, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return fmt.Errorf("Error creating Service: %s", err) + } + + // Store the ID now + id, err := replaceVars(d, config, "locations/{{location}}/namespaces/{{project}}/services/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating Service %q: %#v", d.Id(), res) + + return resourceCloudRunServiceRead(d, meta) +} + +func resourceCloudRunServiceRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + url, err := replaceVars(d, config, "{{CloudRunBasePath}}serving.knative.dev/v1/namespaces/{{project}}/services/{{name}}") + if err != nil { + return err + } + + project, err := getProject(d, config) + if err != nil { + return err + } + res, err := sendRequest(config, "GET", project, url, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("CloudRunService %q", d.Id())) + } + + res, err = resourceCloudRunServiceDecoder(d, meta, res) + if err != nil { + return err + } + + if res == nil { + // Decoding the object has resulted in it being gone. It may be marked deleted + log.Printf("[DEBUG] Removing CloudRunService because it no longer exists.") + d.SetId("") + return nil + } + + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error reading Service: %s", err) + } + + // Terraform must set the top level schema field, but since this object contains collapsed properties + // it's difficult to know what the top level should be. Instead we just loop over the map returned from flatten. + if flattenedProp := flattenCloudRunServiceSpec(res["spec"], d); flattenedProp != nil { + casted := flattenedProp.([]interface{})[0] + if casted != nil { + for k, v := range casted.(map[string]interface{}) { + d.Set(k, v) + } + } + } + if err := d.Set("status", flattenCloudRunServiceStatus(res["status"], d)); err != nil { + return fmt.Errorf("Error reading Service: %s", err) + } + if err := d.Set("metadata", flattenCloudRunServiceMetadata(res["metadata"], d)); err != nil { + return fmt.Errorf("Error reading Service: %s", err) + } + + return nil +} + +func resourceCloudRunServiceUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + obj := make(map[string]interface{}) + specProp, err := expandCloudRunServiceSpec(nil, d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("spec"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, specProp)) { + obj["spec"] = specProp + } + metadataProp, err := expandCloudRunServiceMetadata(d.Get("metadata"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("metadata"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, metadataProp)) { + obj["metadata"] = metadataProp + } + + obj, err = resourceCloudRunServiceEncoder(d, meta, obj) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{CloudRunBasePath}}serving.knative.dev/v1/namespaces/{{project}}/services/{{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating Service %q: %#v", d.Id(), obj) + _, err = sendRequestWithTimeout(config, "PUT", project, url, obj, d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return fmt.Errorf("Error updating Service %q: %s", d.Id(), err) + } + + return resourceCloudRunServiceRead(d, meta) +} + +func resourceCloudRunServiceDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{CloudRunBasePath}}serving.knative.dev/v1/namespaces/{{project}}/services/{{name}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting Service %q", d.Id()) + + res, err := sendRequestWithTimeout(config, "DELETE", project, url, obj, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return handleNotFoundError(err, d, "Service") + } + + log.Printf("[DEBUG] Finished deleting Service %q: %#v", d.Id(), res) + return nil +} + +func resourceCloudRunServiceImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + if err := parseImportId([]string{ + "locations/(?P[^/]+)/namespaces/(?P[^/]+)/services/(?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, "locations/{{location}}/namespaces/{{project}}/services/{{name}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenCloudRunServiceSpec(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["traffic"] = + flattenCloudRunServiceSpecTraffic(original["traffic"], d) + transformed["template"] = + flattenCloudRunServiceSpecTemplate(original["template"], d) + return []interface{}{transformed} +} +func flattenCloudRunServiceSpecTraffic(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{}{ + "revision_name": flattenCloudRunServiceSpecTrafficRevisionName(original["revisionName"], d), + "percent": flattenCloudRunServiceSpecTrafficPercent(original["percent"], d), + "latest_revision": flattenCloudRunServiceSpecTrafficLatestRevision(original["latestRevision"], d), + }) + } + return transformed +} +func flattenCloudRunServiceSpecTrafficRevisionName(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceSpecTrafficPercent(v interface{}, d *schema.ResourceData) interface{} { + // Handles the string fixed64 format + if strVal, ok := v.(string); ok { + if intVal, err := strconv.ParseInt(strVal, 10, 64); err == nil { + return intVal + } // let terraform core handle it if we can't convert the string to an int. + } + return v +} + +func flattenCloudRunServiceSpecTrafficLatestRevision(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceSpecTemplate(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["metadata"] = + flattenCloudRunServiceSpecTemplateMetadata(original["metadata"], d) + transformed["spec"] = + flattenCloudRunServiceSpecTemplateSpec(original["spec"], d) + return []interface{}{transformed} +} +func flattenCloudRunServiceSpecTemplateMetadata(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["labels"] = + flattenCloudRunServiceSpecTemplateMetadataLabels(original["labels"], d) + transformed["generation"] = + flattenCloudRunServiceSpecTemplateMetadataGeneration(original["generation"], d) + transformed["resource_version"] = + flattenCloudRunServiceSpecTemplateMetadataResourceVersion(original["resourceVersion"], d) + transformed["self_link"] = + flattenCloudRunServiceSpecTemplateMetadataSelfLink(original["selfLink"], d) + transformed["uid"] = + flattenCloudRunServiceSpecTemplateMetadataUid(original["uid"], d) + transformed["namespace"] = + flattenCloudRunServiceSpecTemplateMetadataNamespace(original["namespace"], d) + transformed["annotations"] = + flattenCloudRunServiceSpecTemplateMetadataAnnotations(original["annotations"], d) + transformed["name"] = + flattenCloudRunServiceSpecTemplateMetadataName(original["name"], d) + return []interface{}{transformed} +} +func flattenCloudRunServiceSpecTemplateMetadataLabels(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceSpecTemplateMetadataGeneration(v interface{}, d *schema.ResourceData) interface{} { + // Handles the string fixed64 format + if strVal, ok := v.(string); ok { + if intVal, err := strconv.ParseInt(strVal, 10, 64); err == nil { + return intVal + } // let terraform core handle it if we can't convert the string to an int. + } + return v +} + +func flattenCloudRunServiceSpecTemplateMetadataResourceVersion(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceSpecTemplateMetadataSelfLink(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceSpecTemplateMetadataUid(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceSpecTemplateMetadataNamespace(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceSpecTemplateMetadataAnnotations(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceSpecTemplateMetadataName(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceSpecTemplateSpec(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["containers"] = + flattenCloudRunServiceSpecTemplateSpecContainers(original["containers"], d) + transformed["container_concurrency"] = + flattenCloudRunServiceSpecTemplateSpecContainerConcurrency(original["containerConcurrency"], d) + transformed["service_account_name"] = + flattenCloudRunServiceSpecTemplateSpecServiceAccountName(original["serviceAccountName"], d) + transformed["serving_state"] = + flattenCloudRunServiceSpecTemplateSpecServingState(original["servingState"], d) + return []interface{}{transformed} +} +func flattenCloudRunServiceSpecTemplateSpecContainers(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{}{ + "working_dir": flattenCloudRunServiceSpecTemplateSpecContainersWorkingDir(original["workingDir"], d), + "args": flattenCloudRunServiceSpecTemplateSpecContainersArgs(original["args"], d), + "env_from": flattenCloudRunServiceSpecTemplateSpecContainersEnvFrom(original["envFrom"], d), + "image": flattenCloudRunServiceSpecTemplateSpecContainersImage(original["image"], d), + "command": flattenCloudRunServiceSpecTemplateSpecContainersCommand(original["command"], d), + "env": flattenCloudRunServiceSpecTemplateSpecContainersEnv(original["env"], d), + "resources": flattenCloudRunServiceSpecTemplateSpecContainersResources(original["resources"], d), + }) + } + return transformed +} +func flattenCloudRunServiceSpecTemplateSpecContainersWorkingDir(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceSpecTemplateSpecContainersArgs(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceSpecTemplateSpecContainersEnvFrom(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{}{ + "prefix": flattenCloudRunServiceSpecTemplateSpecContainersEnvFromPrefix(original["prefix"], d), + "config_map_ref": flattenCloudRunServiceSpecTemplateSpecContainersEnvFromConfigMapRef(original["configMapRef"], d), + "secret_ref": flattenCloudRunServiceSpecTemplateSpecContainersEnvFromSecretRef(original["secretRef"], d), + }) + } + return transformed +} +func flattenCloudRunServiceSpecTemplateSpecContainersEnvFromPrefix(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceSpecTemplateSpecContainersEnvFromConfigMapRef(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["optional"] = + flattenCloudRunServiceSpecTemplateSpecContainersEnvFromConfigMapRefOptional(original["optional"], d) + transformed["local_object_reference"] = + flattenCloudRunServiceSpecTemplateSpecContainersEnvFromConfigMapRefLocalObjectReference(original["localObjectReference"], d) + return []interface{}{transformed} +} +func flattenCloudRunServiceSpecTemplateSpecContainersEnvFromConfigMapRefOptional(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceSpecTemplateSpecContainersEnvFromConfigMapRefLocalObjectReference(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["name"] = + flattenCloudRunServiceSpecTemplateSpecContainersEnvFromConfigMapRefLocalObjectReferenceName(original["name"], d) + return []interface{}{transformed} +} +func flattenCloudRunServiceSpecTemplateSpecContainersEnvFromConfigMapRefLocalObjectReferenceName(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceSpecTemplateSpecContainersEnvFromSecretRef(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["local_object_reference"] = + flattenCloudRunServiceSpecTemplateSpecContainersEnvFromSecretRefLocalObjectReference(original["localObjectReference"], d) + transformed["optional"] = + flattenCloudRunServiceSpecTemplateSpecContainersEnvFromSecretRefOptional(original["optional"], d) + return []interface{}{transformed} +} +func flattenCloudRunServiceSpecTemplateSpecContainersEnvFromSecretRefLocalObjectReference(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["name"] = + flattenCloudRunServiceSpecTemplateSpecContainersEnvFromSecretRefLocalObjectReferenceName(original["name"], d) + return []interface{}{transformed} +} +func flattenCloudRunServiceSpecTemplateSpecContainersEnvFromSecretRefLocalObjectReferenceName(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceSpecTemplateSpecContainersEnvFromSecretRefOptional(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceSpecTemplateSpecContainersImage(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceSpecTemplateSpecContainersCommand(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceSpecTemplateSpecContainersEnv(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": flattenCloudRunServiceSpecTemplateSpecContainersEnvName(original["name"], d), + "value": flattenCloudRunServiceSpecTemplateSpecContainersEnvValue(original["value"], d), + }) + } + return transformed +} +func flattenCloudRunServiceSpecTemplateSpecContainersEnvName(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceSpecTemplateSpecContainersEnvValue(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceSpecTemplateSpecContainersResources(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["limits"] = + flattenCloudRunServiceSpecTemplateSpecContainersResourcesLimits(original["limits"], d) + transformed["requests"] = + flattenCloudRunServiceSpecTemplateSpecContainersResourcesRequests(original["requests"], d) + return []interface{}{transformed} +} +func flattenCloudRunServiceSpecTemplateSpecContainersResourcesLimits(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceSpecTemplateSpecContainersResourcesRequests(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceSpecTemplateSpecContainerConcurrency(v interface{}, d *schema.ResourceData) interface{} { + // Handles the string fixed64 format + if strVal, ok := v.(string); ok { + if intVal, err := strconv.ParseInt(strVal, 10, 64); err == nil { + return intVal + } // let terraform core handle it if we can't convert the string to an int. + } + return v +} + +func flattenCloudRunServiceSpecTemplateSpecServiceAccountName(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceSpecTemplateSpecServingState(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceStatus(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["conditions"] = + flattenCloudRunServiceStatusConditions(original["conditions"], d) + transformed["url"] = + flattenCloudRunServiceStatusUrl(original["url"], d) + transformed["observed_generation"] = + flattenCloudRunServiceStatusObservedGeneration(original["observedGeneration"], d) + transformed["latest_created_revision_name"] = + flattenCloudRunServiceStatusLatestCreatedRevisionName(original["latestCreatedRevisionName"], d) + transformed["latest_ready_revision_name"] = + flattenCloudRunServiceStatusLatestReadyRevisionName(original["latestReadyRevisionName"], d) + return []interface{}{transformed} +} +func flattenCloudRunServiceStatusConditions(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{}{ + "message": flattenCloudRunServiceStatusConditionsMessage(original["message"], d), + "status": flattenCloudRunServiceStatusConditionsStatus(original["status"], d), + "reason": flattenCloudRunServiceStatusConditionsReason(original["reason"], d), + "type": flattenCloudRunServiceStatusConditionsType(original["type"], d), + }) + } + return transformed +} +func flattenCloudRunServiceStatusConditionsMessage(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceStatusConditionsStatus(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceStatusConditionsReason(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceStatusConditionsType(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceStatusUrl(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceStatusObservedGeneration(v interface{}, d *schema.ResourceData) interface{} { + // Handles the string fixed64 format + if strVal, ok := v.(string); ok { + if intVal, err := strconv.ParseInt(strVal, 10, 64); err == nil { + return intVal + } // let terraform core handle it if we can't convert the string to an int. + } + return v +} + +func flattenCloudRunServiceStatusLatestCreatedRevisionName(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceStatusLatestReadyRevisionName(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceMetadata(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["labels"] = + flattenCloudRunServiceMetadataLabels(original["labels"], d) + transformed["generation"] = + flattenCloudRunServiceMetadataGeneration(original["generation"], d) + transformed["resource_version"] = + flattenCloudRunServiceMetadataResourceVersion(original["resourceVersion"], d) + transformed["self_link"] = + flattenCloudRunServiceMetadataSelfLink(original["selfLink"], d) + transformed["uid"] = + flattenCloudRunServiceMetadataUid(original["uid"], d) + transformed["namespace"] = + flattenCloudRunServiceMetadataNamespace(original["namespace"], d) + transformed["annotations"] = + flattenCloudRunServiceMetadataAnnotations(original["annotations"], d) + return []interface{}{transformed} +} +func flattenCloudRunServiceMetadataLabels(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceMetadataGeneration(v interface{}, d *schema.ResourceData) interface{} { + // Handles the string fixed64 format + if strVal, ok := v.(string); ok { + if intVal, err := strconv.ParseInt(strVal, 10, 64); err == nil { + return intVal + } // let terraform core handle it if we can't convert the string to an int. + } + return v +} + +func flattenCloudRunServiceMetadataResourceVersion(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceMetadataSelfLink(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceMetadataUid(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudRunServiceMetadataNamespace(v interface{}, d *schema.ResourceData) interface{} { + return d.Get("project") +} + +func flattenCloudRunServiceMetadataAnnotations(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func expandCloudRunServiceSpec(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + transformed := make(map[string]interface{}) + transformedTraffic, err := expandCloudRunServiceSpecTraffic(d.Get("traffic"), d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedTraffic); val.IsValid() && !isEmptyValue(val) { + transformed["traffic"] = transformedTraffic + } + + transformedTemplate, err := expandCloudRunServiceSpecTemplate(d.Get("template"), d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedTemplate); val.IsValid() && !isEmptyValue(val) { + transformed["template"] = transformedTemplate + } + + return transformed, nil +} + +func expandCloudRunServiceSpecTraffic(v interface{}, d TerraformResourceData, 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{}) + + transformedRevisionName, err := expandCloudRunServiceSpecTrafficRevisionName(original["revision_name"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedRevisionName); val.IsValid() && !isEmptyValue(val) { + transformed["revisionName"] = transformedRevisionName + } + + transformedPercent, err := expandCloudRunServiceSpecTrafficPercent(original["percent"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedPercent); val.IsValid() && !isEmptyValue(val) { + transformed["percent"] = transformedPercent + } + + transformedLatestRevision, err := expandCloudRunServiceSpecTrafficLatestRevision(original["latest_revision"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedLatestRevision); val.IsValid() && !isEmptyValue(val) { + transformed["latestRevision"] = transformedLatestRevision + } + + req = append(req, transformed) + } + return req, nil +} + +func expandCloudRunServiceSpecTrafficRevisionName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunServiceSpecTrafficPercent(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunServiceSpecTrafficLatestRevision(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunServiceSpecTemplate(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{}) + + transformedMetadata, err := expandCloudRunServiceSpecTemplateMetadata(original["metadata"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedMetadata); val.IsValid() && !isEmptyValue(val) { + transformed["metadata"] = transformedMetadata + } + + transformedSpec, err := expandCloudRunServiceSpecTemplateSpec(original["spec"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedSpec); val.IsValid() && !isEmptyValue(val) { + transformed["spec"] = transformedSpec + } + + return transformed, nil +} + +func expandCloudRunServiceSpecTemplateMetadata(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{}) + + transformedLabels, err := expandCloudRunServiceSpecTemplateMetadataLabels(original["labels"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedLabels); val.IsValid() && !isEmptyValue(val) { + transformed["labels"] = transformedLabels + } + + transformedGeneration, err := expandCloudRunServiceSpecTemplateMetadataGeneration(original["generation"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedGeneration); val.IsValid() && !isEmptyValue(val) { + transformed["generation"] = transformedGeneration + } + + transformedResourceVersion, err := expandCloudRunServiceSpecTemplateMetadataResourceVersion(original["resource_version"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedResourceVersion); val.IsValid() && !isEmptyValue(val) { + transformed["resourceVersion"] = transformedResourceVersion + } + + transformedSelfLink, err := expandCloudRunServiceSpecTemplateMetadataSelfLink(original["self_link"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedSelfLink); val.IsValid() && !isEmptyValue(val) { + transformed["selfLink"] = transformedSelfLink + } + + transformedUid, err := expandCloudRunServiceSpecTemplateMetadataUid(original["uid"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedUid); val.IsValid() && !isEmptyValue(val) { + transformed["uid"] = transformedUid + } + + transformedNamespace, err := expandCloudRunServiceSpecTemplateMetadataNamespace(original["namespace"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedNamespace); val.IsValid() && !isEmptyValue(val) { + transformed["namespace"] = transformedNamespace + } + + transformedAnnotations, err := expandCloudRunServiceSpecTemplateMetadataAnnotations(original["annotations"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedAnnotations); val.IsValid() && !isEmptyValue(val) { + transformed["annotations"] = transformedAnnotations + } + + transformedName, err := expandCloudRunServiceSpecTemplateMetadataName(original["name"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedName); val.IsValid() && !isEmptyValue(val) { + transformed["name"] = transformedName + } + + return transformed, nil +} + +func expandCloudRunServiceSpecTemplateMetadataLabels(v interface{}, d TerraformResourceData, 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 expandCloudRunServiceSpecTemplateMetadataGeneration(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunServiceSpecTemplateMetadataResourceVersion(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunServiceSpecTemplateMetadataSelfLink(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunServiceSpecTemplateMetadataUid(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +// If the property hasn't been explicitly set in config use the project defined by the provider or env. +func expandCloudRunServiceSpecTemplateMetadataNamespace(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + if v == nil { + project, err := getProject(d, config) + if err != nil { + return project, nil + } + } + return v, nil +} + +func expandCloudRunServiceSpecTemplateMetadataAnnotations(v interface{}, d TerraformResourceData, 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 expandCloudRunServiceSpecTemplateMetadataName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunServiceSpecTemplateSpec(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{}) + + transformedContainers, err := expandCloudRunServiceSpecTemplateSpecContainers(original["containers"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedContainers); val.IsValid() && !isEmptyValue(val) { + transformed["containers"] = transformedContainers + } + + transformedContainerConcurrency, err := expandCloudRunServiceSpecTemplateSpecContainerConcurrency(original["container_concurrency"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedContainerConcurrency); val.IsValid() && !isEmptyValue(val) { + transformed["containerConcurrency"] = transformedContainerConcurrency + } + + transformedServiceAccountName, err := expandCloudRunServiceSpecTemplateSpecServiceAccountName(original["service_account_name"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedServiceAccountName); val.IsValid() && !isEmptyValue(val) { + transformed["serviceAccountName"] = transformedServiceAccountName + } + + transformedServingState, err := expandCloudRunServiceSpecTemplateSpecServingState(original["serving_state"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedServingState); val.IsValid() && !isEmptyValue(val) { + transformed["servingState"] = transformedServingState + } + + return transformed, nil +} + +func expandCloudRunServiceSpecTemplateSpecContainers(v interface{}, d TerraformResourceData, 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{}) + + transformedWorkingDir, err := expandCloudRunServiceSpecTemplateSpecContainersWorkingDir(original["working_dir"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedWorkingDir); val.IsValid() && !isEmptyValue(val) { + transformed["workingDir"] = transformedWorkingDir + } + + transformedArgs, err := expandCloudRunServiceSpecTemplateSpecContainersArgs(original["args"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedArgs); val.IsValid() && !isEmptyValue(val) { + transformed["args"] = transformedArgs + } + + transformedEnvFrom, err := expandCloudRunServiceSpecTemplateSpecContainersEnvFrom(original["env_from"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedEnvFrom); val.IsValid() && !isEmptyValue(val) { + transformed["envFrom"] = transformedEnvFrom + } + + transformedImage, err := expandCloudRunServiceSpecTemplateSpecContainersImage(original["image"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedImage); val.IsValid() && !isEmptyValue(val) { + transformed["image"] = transformedImage + } + + transformedCommand, err := expandCloudRunServiceSpecTemplateSpecContainersCommand(original["command"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedCommand); val.IsValid() && !isEmptyValue(val) { + transformed["command"] = transformedCommand + } + + transformedEnv, err := expandCloudRunServiceSpecTemplateSpecContainersEnv(original["env"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedEnv); val.IsValid() && !isEmptyValue(val) { + transformed["env"] = transformedEnv + } + + transformedResources, err := expandCloudRunServiceSpecTemplateSpecContainersResources(original["resources"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedResources); val.IsValid() && !isEmptyValue(val) { + transformed["resources"] = transformedResources + } + + req = append(req, transformed) + } + return req, nil +} + +func expandCloudRunServiceSpecTemplateSpecContainersWorkingDir(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunServiceSpecTemplateSpecContainersArgs(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunServiceSpecTemplateSpecContainersEnvFrom(v interface{}, d TerraformResourceData, 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{}) + + transformedPrefix, err := expandCloudRunServiceSpecTemplateSpecContainersEnvFromPrefix(original["prefix"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedPrefix); val.IsValid() && !isEmptyValue(val) { + transformed["prefix"] = transformedPrefix + } + + transformedConfigMapRef, err := expandCloudRunServiceSpecTemplateSpecContainersEnvFromConfigMapRef(original["config_map_ref"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedConfigMapRef); val.IsValid() && !isEmptyValue(val) { + transformed["configMapRef"] = transformedConfigMapRef + } + + transformedSecretRef, err := expandCloudRunServiceSpecTemplateSpecContainersEnvFromSecretRef(original["secret_ref"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedSecretRef); val.IsValid() && !isEmptyValue(val) { + transformed["secretRef"] = transformedSecretRef + } + + req = append(req, transformed) + } + return req, nil +} + +func expandCloudRunServiceSpecTemplateSpecContainersEnvFromPrefix(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunServiceSpecTemplateSpecContainersEnvFromConfigMapRef(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{}) + + transformedOptional, err := expandCloudRunServiceSpecTemplateSpecContainersEnvFromConfigMapRefOptional(original["optional"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedOptional); val.IsValid() && !isEmptyValue(val) { + transformed["optional"] = transformedOptional + } + + transformedLocalObjectReference, err := expandCloudRunServiceSpecTemplateSpecContainersEnvFromConfigMapRefLocalObjectReference(original["local_object_reference"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedLocalObjectReference); val.IsValid() && !isEmptyValue(val) { + transformed["localObjectReference"] = transformedLocalObjectReference + } + + return transformed, nil +} + +func expandCloudRunServiceSpecTemplateSpecContainersEnvFromConfigMapRefOptional(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunServiceSpecTemplateSpecContainersEnvFromConfigMapRefLocalObjectReference(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{}) + + transformedName, err := expandCloudRunServiceSpecTemplateSpecContainersEnvFromConfigMapRefLocalObjectReferenceName(original["name"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedName); val.IsValid() && !isEmptyValue(val) { + transformed["name"] = transformedName + } + + return transformed, nil +} + +func expandCloudRunServiceSpecTemplateSpecContainersEnvFromConfigMapRefLocalObjectReferenceName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunServiceSpecTemplateSpecContainersEnvFromSecretRef(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{}) + + transformedLocalObjectReference, err := expandCloudRunServiceSpecTemplateSpecContainersEnvFromSecretRefLocalObjectReference(original["local_object_reference"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedLocalObjectReference); val.IsValid() && !isEmptyValue(val) { + transformed["localObjectReference"] = transformedLocalObjectReference + } + + transformedOptional, err := expandCloudRunServiceSpecTemplateSpecContainersEnvFromSecretRefOptional(original["optional"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedOptional); val.IsValid() && !isEmptyValue(val) { + transformed["optional"] = transformedOptional + } + + return transformed, nil +} + +func expandCloudRunServiceSpecTemplateSpecContainersEnvFromSecretRefLocalObjectReference(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{}) + + transformedName, err := expandCloudRunServiceSpecTemplateSpecContainersEnvFromSecretRefLocalObjectReferenceName(original["name"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedName); val.IsValid() && !isEmptyValue(val) { + transformed["name"] = transformedName + } + + return transformed, nil +} + +func expandCloudRunServiceSpecTemplateSpecContainersEnvFromSecretRefLocalObjectReferenceName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunServiceSpecTemplateSpecContainersEnvFromSecretRefOptional(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunServiceSpecTemplateSpecContainersImage(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunServiceSpecTemplateSpecContainersCommand(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunServiceSpecTemplateSpecContainersEnv(v interface{}, d TerraformResourceData, 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 := expandCloudRunServiceSpecTemplateSpecContainersEnvName(original["name"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedName); val.IsValid() && !isEmptyValue(val) { + transformed["name"] = transformedName + } + + transformedValue, err := expandCloudRunServiceSpecTemplateSpecContainersEnvValue(original["value"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedValue); val.IsValid() && !isEmptyValue(val) { + transformed["value"] = transformedValue + } + + req = append(req, transformed) + } + return req, nil +} + +func expandCloudRunServiceSpecTemplateSpecContainersEnvName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunServiceSpecTemplateSpecContainersEnvValue(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunServiceSpecTemplateSpecContainersResources(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{}) + + transformedLimits, err := expandCloudRunServiceSpecTemplateSpecContainersResourcesLimits(original["limits"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedLimits); val.IsValid() && !isEmptyValue(val) { + transformed["limits"] = transformedLimits + } + + transformedRequests, err := expandCloudRunServiceSpecTemplateSpecContainersResourcesRequests(original["requests"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedRequests); val.IsValid() && !isEmptyValue(val) { + transformed["requests"] = transformedRequests + } + + return transformed, nil +} + +func expandCloudRunServiceSpecTemplateSpecContainersResourcesLimits(v interface{}, d TerraformResourceData, 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 expandCloudRunServiceSpecTemplateSpecContainersResourcesRequests(v interface{}, d TerraformResourceData, 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 expandCloudRunServiceSpecTemplateSpecContainerConcurrency(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunServiceSpecTemplateSpecServiceAccountName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunServiceSpecTemplateSpecServingState(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunServiceMetadata(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{}) + + transformedLabels, err := expandCloudRunServiceMetadataLabels(original["labels"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedLabels); val.IsValid() && !isEmptyValue(val) { + transformed["labels"] = transformedLabels + } + + transformedGeneration, err := expandCloudRunServiceMetadataGeneration(original["generation"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedGeneration); val.IsValid() && !isEmptyValue(val) { + transformed["generation"] = transformedGeneration + } + + transformedResourceVersion, err := expandCloudRunServiceMetadataResourceVersion(original["resource_version"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedResourceVersion); val.IsValid() && !isEmptyValue(val) { + transformed["resourceVersion"] = transformedResourceVersion + } + + transformedSelfLink, err := expandCloudRunServiceMetadataSelfLink(original["self_link"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedSelfLink); val.IsValid() && !isEmptyValue(val) { + transformed["selfLink"] = transformedSelfLink + } + + transformedUid, err := expandCloudRunServiceMetadataUid(original["uid"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedUid); val.IsValid() && !isEmptyValue(val) { + transformed["uid"] = transformedUid + } + + transformedNamespace, err := expandCloudRunServiceMetadataNamespace(original["namespace"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedNamespace); val.IsValid() && !isEmptyValue(val) { + transformed["namespace"] = transformedNamespace + } + + transformedAnnotations, err := expandCloudRunServiceMetadataAnnotations(original["annotations"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedAnnotations); val.IsValid() && !isEmptyValue(val) { + transformed["annotations"] = transformedAnnotations + } + + return transformed, nil +} + +func expandCloudRunServiceMetadataLabels(v interface{}, d TerraformResourceData, 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 expandCloudRunServiceMetadataGeneration(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunServiceMetadataResourceVersion(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunServiceMetadataSelfLink(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunServiceMetadataUid(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +// If the property hasn't been explicitly set in config use the project defined by the provider or env. +func expandCloudRunServiceMetadataNamespace(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + if v == nil { + project, err := getProject(d, config) + if err != nil { + return project, nil + } + } + return v, nil +} + +func expandCloudRunServiceMetadataAnnotations(v interface{}, d TerraformResourceData, 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 resourceCloudRunServiceEncoder(d *schema.ResourceData, meta interface{}, obj map[string]interface{}) (map[string]interface{}, error) { + name := d.Get("name").(string) + metadata := obj["metadata"].(map[string]interface{}) + metadata["name"] = name + + // The only acceptable version/kind right now + obj["apiVersion"] = "serving.knative.dev/v1" + obj["kind"] = "Service" + return obj, nil +} + +func resourceCloudRunServiceDecoder(d *schema.ResourceData, meta interface{}, res map[string]interface{}) (map[string]interface{}, error) { + // metadata is not present if the API returns an error + if obj, ok := res["metadata"]; ok { + if meta, ok := obj.(map[string]interface{}); ok { + res["name"] = meta["name"] + } else { + return nil, fmt.Errorf("Unable to decode 'metadata' block from API response.") + } + } + return res, nil +} diff --git a/google/resource_cloud_run_service_test.go b/google/resource_cloud_run_service_test.go index 427c19265da..df48382ad98 100644 --- a/google/resource_cloud_run_service_test.go +++ b/google/resource_cloud_run_service_test.go @@ -1,4 +1,153 @@ package google -// Because Cloud Run is still in beta, we can't run any of the tests that call that -// resource without vendoring in the full beta provider. +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccCloudRunService_cloudRunServiceUpdate(t *testing.T) { + t.Parallel() + + project := getTestProjectFromEnv() + name := "tftest-cloudrun-" + acctest.RandString(6) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCloudRunService_cloudRunServiceUpdate(name, project, "10"), + }, + { + ResourceName: "google_cloud_run_service.default", + ImportStateId: fmt.Sprintf("locations/us-central1/namespaces/%s/services/%s", project, name), + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"metadata.0.resource_version", "status.0.conditions"}, + }, + { + Config: testAccCloudRunService_cloudRunServiceUpdate(name, project, "50"), + }, + { + ResourceName: "google_cloud_run_service.default", + ImportStateId: fmt.Sprintf("locations/us-central1/namespaces/%s/services/%s", project, name), + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"metadata.0.resource_version", "status.0.conditions"}, + }, + }, + }) +} + +func TestAccCloudRunService_cloudRunServiceSql(t *testing.T) { + t.Parallel() + + project := getTestProjectFromEnv() + name := "tftest-cloudrun-" + acctest.RandString(6) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCloudRunService_cloudRunServiceSql(name, project), + }, + { + ResourceName: "google_cloud_run_service.default", + ImportStateId: fmt.Sprintf("locations/us-central1/namespaces/%s/services/%s", project, name), + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"metadata.0.resource_version", "status.0.conditions"}, + }, + }, + }) +} + +func testAccCloudRunService_cloudRunServiceUpdate(name, project, concurrency string) string { + return fmt.Sprintf(` +resource "google_cloud_run_service" "default" { + name = "%s" + location = "us-central1" + + metadata { + namespace = "%s" + } + + template { + spec { + containers { + image = "gcr.io/cloudrun/hello" + args = ["arrgs"] + } + container_concurrency = %s + } + } + + traffic { + percent = 100 + latest_revision = true + } +} +`, name, project, concurrency) +} + +func testAccCloudRunService_cloudRunServiceSql(name, project string) string { + return fmt.Sprintf(` +data "google_project" "project" {} + +resource "google_sql_database_instance" "instance" { + name = "tf-test-%s" + region = "us-east1" + settings { + tier = "D0" + } +} + +resource "google_cloud_run_service" "default" { + location = "us-east1" + name = "%s" + + metadata { + namespace = "%s" + labels = { + "cloud.googleapis.com/location" = "us-east1" + "foo" = "bar" + } + } + + template { + metadata { + annotations = { + "autoscaling.knative.dev/maxScale" = "1000" + "run.googleapis.com/cloudsql-instances" = "%s:us-east1:${google_sql_database_instance.instance.name}" + "run.googleapis.com/client-name" = "cloud-console" + } + } + + spec { + service_account_name = "${data.google_project.project.number}-compute@developer.gserviceaccount.com" + + containers { + image = "gcr.io/cloudrun/hello" + args = ["arrg2", "pirate"] + resources { + limits = { + cpu = "1000m" + memory = "256Mi" + } + } + } + container_concurrency = 10 + } + } + + traffic { + percent = 100 + latest_revision = true + } +} +`, acctest.RandString(6), name, project, project) +} diff --git a/google/resource_compute_backend_service.go b/google/resource_compute_backend_service.go index 28e644b6da5..fbb13d85a7a 100644 --- a/google/resource_compute_backend_service.go +++ b/google/resource_compute_backend_service.go @@ -1245,8 +1245,6 @@ func flattenComputeBackendServiceIap(v interface{}, d *schema.ResourceData) inte transformed := make(map[string]interface{}) transformed["oauth2_client_id"] = flattenComputeBackendServiceIapOauth2ClientId(original["oauth2ClientId"], d) - transformed["oauth2_client_secret"] = - flattenComputeBackendServiceIapOauth2ClientSecret(original["oauth2ClientSecret"], d) transformed["oauth2_client_secret_sha256"] = flattenComputeBackendServiceIapOauth2ClientSecretSha256(original["oauth2ClientSecretSha256"], d) return []interface{}{transformed} @@ -1255,10 +1253,6 @@ func flattenComputeBackendServiceIapOauth2ClientId(v interface{}, d *schema.Reso return v } -func flattenComputeBackendServiceIapOauth2ClientSecret(v interface{}, d *schema.ResourceData) interface{} { - return d.Get("iap.0.oauth2_client_secret") -} - func flattenComputeBackendServiceIapOauth2ClientSecretSha256(v interface{}, d *schema.ResourceData) interface{} { return v } diff --git a/google/resource_compute_snapshot.go b/google/resource_compute_snapshot.go index 14ac85c6925..a9c81741ae6 100644 --- a/google/resource_compute_snapshot.go +++ b/google/resource_compute_snapshot.go @@ -526,16 +526,10 @@ func flattenComputeSnapshotSnapshotEncryptionKey(v interface{}, d *schema.Resour return nil } transformed := make(map[string]interface{}) - transformed["raw_key"] = - flattenComputeSnapshotSnapshotEncryptionKeyRawKey(original["rawKey"], d) transformed["sha256"] = flattenComputeSnapshotSnapshotEncryptionKeySha256(original["sha256"], d) return []interface{}{transformed} } -func flattenComputeSnapshotSnapshotEncryptionKeyRawKey(v interface{}, d *schema.ResourceData) interface{} { - return d.Get("snapshot_encryption_key.0.raw_key") -} - func flattenComputeSnapshotSnapshotEncryptionKeySha256(v interface{}, d *schema.ResourceData) interface{} { return v } diff --git a/google/transport.go b/google/transport.go index 2422085f163..719fa2d6260 100644 --- a/google/transport.go +++ b/google/transport.go @@ -3,6 +3,7 @@ package google import ( "bytes" "encoding/json" + "errors" "fmt" "net/http" "net/url" @@ -127,13 +128,33 @@ func addQueryParams(rawurl string, params map[string]string) (string, error) { } func replaceVars(d TerraformResourceData, config *Config, linkTmpl string) (string, error) { + return replaceVarsRecursive(d, config, linkTmpl, 0) +} + +// replaceVars must be done recursively because there are baseUrls that can contain references to regions +// (eg cloudrun service) there aren't any cases known for 2+ recursion but we will track a run away +// substitution as 10+ calls to allow for future use cases. +func replaceVarsRecursive(d TerraformResourceData, config *Config, linkTmpl string, depth int) (string, error) { + if depth > 10 { + return "", errors.New("Recursive substitution detcted") + } + // https://github.com/google/re2/wiki/Syntax re := regexp.MustCompile("{{([%[:word:]]+)}}") f, err := buildReplacementFunc(re, d, config, linkTmpl) if err != nil { return "", err } - return re.ReplaceAllStringFunc(linkTmpl, f), nil + final, err := re.ReplaceAllStringFunc(linkTmpl, f), nil + if err != nil { + return "", err + } + + if re.Match([]byte(final)) { + return replaceVarsRecursive(d, config, final, depth+1) + } + + return final, nil } // This function replaces references to Terraform properties (in the form of {{var}}) with their value in Terraform @@ -178,6 +199,7 @@ func buildReplacementFunc(re *regexp.Regexp, d TerraformResourceData, config *Co } f := func(s string) string { + m := re.FindStringSubmatch(s)[1] if m == "project" { return project @@ -213,7 +235,6 @@ func buildReplacementFunc(re *regexp.Regexp, d TerraformResourceData, config *Co return f.String() } } - return "" } diff --git a/google/transport_test.go b/google/transport_test.go index cc499e6ca68..cf9b4661a48 100644 --- a/google/transport_test.go +++ b/google/transport_test.go @@ -132,6 +132,25 @@ func TestReplaceVars(t *testing.T) { }, Expected: "projects/project1/zones/zone1/instances/instance1", }, + "zonal schema recursive replacement": { + Template: "projects/{{project}}/zones/{{zone}}/instances/{{name}}", + SchemaValues: map[string]interface{}{ + "project": "project1", + "zone": "wrapper{{innerzone}}wrapper", + "name": "instance1", + "innerzone": "inner", + }, + Expected: "projects/project1/zones/wrapperinnerwrapper/instances/instance1", + }, + "base path recursive replacement": { + Template: "{{CloudRunBasePath}}namespaces/{{project}}/services", + Config: &Config{ + Project: "default-project", + Region: "default-region", + CloudRunBasePath: "https://{{region}}-run.googleapis.com/", + }, + Expected: "https://default-region-run.googleapis.com/namespaces/default-project/services", + }, } for tn, tc := range cases { diff --git a/website/docs/r/cloud_run_domain_mapping.html.markdown b/website/docs/r/cloud_run_domain_mapping.html.markdown new file mode 100644 index 00000000000..4175c5d4366 --- /dev/null +++ b/website/docs/r/cloud_run_domain_mapping.html.markdown @@ -0,0 +1,243 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** 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. +# +# ---------------------------------------------------------------------------- +subcategory: "Cloud Run" +layout: "google" +page_title: "Google: google_cloud_run_domain_mapping" +sidebar_current: "docs-google-cloud-run-domain-mapping" +description: |- + Resource to hold the state and status of a user's domain mapping. +--- + +# google\_cloud\_run\_domain\_mapping + +Resource to hold the state and status of a user's domain mapping. + + +To get more information about DomainMapping, see: + +* [API documentation](https://cloud.google.com/run/docs/reference/rest/v1alpha1/projects.locations.domainmappings) +* How-to Guides + * [Official Documentation](https://cloud.google.com/run/docs/mapping-custom-domains) + + +## Example Usage - Cloud Run Domain Mapping Basic + + +```hcl + +resource "google_cloud_run_service" "default" { + name = "tftest-cloudrun" + location = "us-central1" + + metadata { + namespace = "my-project-name" + } + + template { + spec { + containers { + image = "gcr.io/cloudrun/hello" + } + } + } +} + +resource "google_cloud_run_domain_mapping" "default" { + location = "us-central1" + name = "verified-domain.com" + + metadata { + namespace = "my-project-name" + } + + spec { + route_name = google_cloud_run_service.default.name + } +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `name` - + (Required) + Name should be a verified domain + +* `spec` - + (Required) + The spec for this DomainMapping. Structure is documented below. + +* `metadata` - + (Required) + Metadata associated with this DomainMapping. Structure is documented below. + +* `location` - + (Required) + The location of the cloud run instance. eg us-central1 + + +The `spec` block supports: + +* `force_override` - + (Optional) + If set, the mapping will override any mapping set before this spec was set. + It is recommended that the user leaves this empty to receive an error + warning about a potential conflict and only set it once the respective UI + has given such a warning. + +* `route_name` - + (Required) + The name of the Cloud Run Service that this DomainMapping applies to. + The route must exist. + +* `certificate_mode` - + (Optional) + The mode of the certificate. + +The `metadata` block supports: + +* `labels` - + (Optional) + Map of string keys and values that can be used to organize and categorize + (scope and select) objects. May match selectors of replication controllers + and routes. + More info: http://kubernetes.io/docs/user-guide/labels + +* `generation` - + A sequence number representing a specific generation of the desired state. + +* `resource_version` - + An opaque value that represents the internal version of this object that + can be used by clients to determine when objects have changed. May be used + for optimistic concurrency, change detection, and the watch operation on a + resource or set of resources. They may only be valid for a + particular resource or set of resources. + More info: + https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency + +* `self_link` - + SelfLink is a URL representing this object. + +* `uid` - + UID is a unique id generated by the server on successful creation of a resource and is not + allowed to change on PUT operations. + More info: http://kubernetes.io/docs/user-guide/identifiers#uids + +* `namespace` - + (Required) + In Cloud Run the namespace must be equal to either the + project ID or project number. + +* `annotations` - + (Optional) + Annotations is a key value map stored with a resource that + may be set by external tools to store and retrieve arbitrary metadata. More + info: http://kubernetes.io/docs/user-guide/annotations + +- - - + + +* `project` - (Optional) The ID of the project in which the resource belongs. + If it is not provided, the provider project is used. + + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + + +* `status` - + The current status of the DomainMapping. Structure is documented below. + + +The `status` block contains: + +* `conditions` - + Array of observed DomainMappingConditions, indicating the current state + of the DomainMapping. Structure is documented below. + +* `observed_generation` - + ObservedGeneration is the 'Generation' of the DomainMapping that + was last processed by the controller. + +* `resource_records` - + (Optional) + The resource records required to configure this domain mapping. These + records must be added to the domain's DNS configuration in order to + serve the application via this domain mapping. Structure is documented below. + +* `mapped_route_name` - + The name of the route that the mapping currently points to. + + +The `conditions` block contains: + +* `message` - + Human readable message indicating details about the current status. + +* `status` - + Status of the condition, one of True, False, Unknown. + +* `reason` - + One-word CamelCase reason for the condition's current status. + +* `type` - + Type of domain mapping condition. + +The `resource_records` block supports: + +* `type` - + (Optional) + Resource record type. Example: `AAAA`. + +* `rrdata` - + Data for this record. Values vary by record type, as defined in RFC 1035 + (section 5) and RFC 1034 (section 3.6.1). + +* `name` - + Relative name of the object affected by this record. Only applicable for + `CNAME` records. Example: 'www'. + +## Timeouts + +This resource provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - Default is 4 minutes. +- `update` - Default is 4 minutes. +- `delete` - Default is 4 minutes. + +## Import + +DomainMapping can be imported using any of these accepted formats: + +``` +$ terraform import google_cloud_run_domain_mapping.default locations/{{location}}/namespaces/{{project}}/domainmappings/{{name}} +$ terraform import google_cloud_run_domain_mapping.default {{location}}/{{project}}/{{name}} +$ terraform import google_cloud_run_domain_mapping.default {{location}}/{{name}} +``` + +-> 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. + +## User Project Overrides + +This resource supports [User Project Overrides](https://www.terraform.io/docs/providers/google/guides/provider_reference.html#user_project_override). diff --git a/website/docs/r/cloud_run_service.html.markdown b/website/docs/r/cloud_run_service.html.markdown new file mode 100644 index 00000000000..41b6db4f27c --- /dev/null +++ b/website/docs/r/cloud_run_service.html.markdown @@ -0,0 +1,492 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** 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. +# +# ---------------------------------------------------------------------------- +subcategory: "Cloud Run" +layout: "google" +page_title: "Google: google_cloud_run_service" +sidebar_current: "docs-google-cloud-run-service" +description: |- + Service acts as a top-level container that manages a set of Routes and + Configurations which implement a network service. +--- + +# google\_cloud\_run\_service + +Service acts as a top-level container that manages a set of Routes and +Configurations which implement a network service. Service exists to provide a +singular abstraction which can be access controlled, reasoned about, and +which encapsulates software lifecycle decisions such as rollout policy and +team resource ownership. Service acts only as an orchestrator of the +underlying Routes and Configurations (much as a kubernetes Deployment +orchestrates ReplicaSets). + +The Service's controller will track the statuses of its owned Configuration +and Route, reflecting their statuses and conditions as its own. + +See also: +https://github.com/knative/serving/blob/master/docs/spec/overview.md#service + + +To get more information about Service, see: + +* [API documentation](https://cloud.google.com/run/docs/reference/rest/v1/projects.locations.services) +* How-to Guides + * [Official Documentation](https://cloud.google.com/run/docs/) + +## Example Usage - Cloud Run Service Basic + + +```hcl +resource "google_cloud_run_service" "default" { + name = "tftest-cloudrun" + location = "us-central1" + + metadata { + namespace = "my-project-name" + } + + template { + spec { + containers { + image = "gcr.io/cloudrun/hello" + } + } + } + + traffic { + percent = 100 + latest_revision = true + } +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `name` - + (Required) + Name must be unique within a namespace, within a Cloud Run region. + Is required when creating resources. Name is primarily intended + for creation idempotence and configuration definition. Cannot be updated. + More info: http://kubernetes.io/docs/user-guide/identifiers#names + +* `metadata` - + (Required) + Metadata associated with this Service, including name, namespace, labels, + and annotations. Structure is documented below. + +* `location` - + (Required) + The location of the cloud run instance. eg us-central1 + + + +The `traffic` block supports: + +* `revision_name` - + (Optional) + RevisionName of a specific revision to which to send this portion of traffic. + +* `percent` - + (Required) + Percent specifies percent of the traffic to this Revision or Configuration. + +* `latest_revision` - + (Optional) + LatestRevision may be optionally provided to indicate that the latest ready + Revision of the Configuration should be used for this traffic target. When + provided LatestRevision must be true if RevisionName is empty; it must be + false when RevisionName is non-empty. + +The `template` block supports: + +* `metadata` - + (Optional) + Optional metadata for this Revision, including labels and annotations. + Name will be generated by the Configuration. To set minimum instances + for this revision, use the "autoscaling.knative.dev/minScale" annotation + key. To set maximum instances for this revision, use the + "autoscaling.knative.dev/maxScale" annotation key. To set Cloud SQL + connections for the revision, use the "run.googleapis.com/cloudsql-instances" + annotation key. Structure is documented below. + +* `spec` - + (Required) + RevisionSpec holds the desired state of the Revision (from the client). Structure is documented below. + + +The `metadata` block supports: + +* `labels` - + (Optional) + Map of string keys and values that can be used to organize and categorize + (scope and select) objects. May match selectors of replication controllers + and routes. + More info: http://kubernetes.io/docs/user-guide/labels + +* `generation` - + A sequence number representing a specific generation of the desired state. + +* `resource_version` - + An opaque value that represents the internal version of this object that + can be used by clients to determine when objects have changed. May be used + for optimistic concurrency, change detection, and the watch operation on a + resource or set of resources. They may only be valid for a + particular resource or set of resources. + More info: + https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency + +* `self_link` - + SelfLink is a URL representing this object. + +* `uid` - + UID is a unique id generated by the server on successful creation of a resource and is not + allowed to change on PUT operations. + More info: http://kubernetes.io/docs/user-guide/identifiers#uids + +* `namespace` - + (Optional) + In Cloud Run the namespace must be equal to either the + project ID or project number. + +* `annotations` - + (Optional) + Annotations is a key value map stored with a resource that + may be set by external tools to store and retrieve arbitrary metadata. More + info: http://kubernetes.io/docs/user-guide/annotations + +* `name` - + (Optional) + Name must be unique within a namespace, within a Cloud Run region. + Is required when creating resources. Name is primarily intended + for creation idempotence and configuration definition. Cannot be updated. + More info: http://kubernetes.io/docs/user-guide/identifiers#names + +The `spec` block supports: + +* `containers` - + (Required) + Container defines the unit of execution for this Revision. + In the context of a Revision, we disallow a number of the fields of + this Container, including: name, ports, and volumeMounts. + The runtime contract is documented here: + https://github.com/knative/serving/blob/master/docs/runtime-contract.md Structure is documented below. + +* `container_concurrency` - + (Optional) + ContainerConcurrency specifies the maximum allowed in-flight (concurrent) + requests per container of the Revision. Values are: + - `0` thread-safe, the system should manage the max concurrency. This is + the default value. + - `1` not-thread-safe. Single concurrency + - `2-N` thread-safe, max concurrency of N + +* `service_account_name` - + (Optional) + Email address of the IAM service account associated with the revision of the + service. The service account represents the identity of the running revision, + and determines what permissions the revision has. If not provided, the revision + will use the project's default service account. + +* `serving_state` - + ServingState holds a value describing the state the resources + are in for this Revision. + It is expected + that the system will manipulate this based on routability and load. + + +The `containers` block supports: + +* `working_dir` - + (Optional, Deprecated) + Container's working directory. + If not specified, the container runtime's default will be used, which + might be configured in the container image. + +* `args` - + (Optional) + Arguments to the entrypoint. + The docker image's CMD is used if this is not provided. + Variable references $(VAR_NAME) are expanded using the container's + environment. If a variable cannot be resolved, the reference in the input + string will be unchanged. The $(VAR_NAME) syntax can be escaped with a + double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, + regardless of whether the variable exists or not. + More info: + https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + +* `env_from` - + (Optional, Deprecated) + List of sources to populate environment variables in the container. + All invalid keys will be reported as an event when the container is starting. + When a key exists in multiple sources, the value associated with the last source will + take precedence. Values defined by an Env with a duplicate key will take + precedence. Structure is documented below. + +* `image` - + (Required) + Docker image name. This is most often a reference to a container located + in the container registry, such as gcr.io/cloudrun/hello + More info: https://kubernetes.io/docs/concepts/containers/images + +* `command` - + (Optional) + Entrypoint array. Not executed within a shell. + The docker image's ENTRYPOINT is used if this is not provided. + Variable references $(VAR_NAME) are expanded using the container's + environment. If a variable cannot be resolved, the reference in the input + string will be unchanged. The $(VAR_NAME) syntax can be escaped with a + double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, + regardless of whether the variable exists or not. + More info: + https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + +* `env` - + (Optional) + List of environment variables to set in the container. Structure is documented below. + +* `resources` - + (Optional) + Compute Resources required by this container. Used to set values such as max memory + More info: + https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources Structure is documented below. + + +The `env_from` block supports: + +* `prefix` - + (Optional) + An optional identifier to prepend to each key in the ConfigMap. + +* `config_map_ref` - + (Optional) + The ConfigMap to select from. Structure is documented below. + +* `secret_ref` - + (Optional) + The Secret to select from. Structure is documented below. + + +The `config_map_ref` block supports: + +* `optional` - + (Optional) + Specify whether the ConfigMap must be defined + +* `local_object_reference` - + (Optional) + The ConfigMap to select from. Structure is documented below. + + +The `local_object_reference` block supports: + +* `name` - + (Required) + Name of the referent. + More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + +The `secret_ref` block supports: + +* `local_object_reference` - + (Optional) + The Secret to select from. Structure is documented below. + +* `optional` - + (Optional) + Specify whether the Secret must be defined + + +The `local_object_reference` block supports: + +* `name` - + (Required) + Name of the referent. + More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + +The `env` block supports: + +* `name` - + (Optional) + Name of the environment variable. + +* `value` - + (Optional) + Variable references $(VAR_NAME) are expanded + using the previous defined environment variables in the container and + any route environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. The $(VAR_NAME) + syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped + references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + +The `resources` block supports: + +* `limits` - + (Optional) + Limits describes the maximum amount of compute resources allowed. + The values of the map is string form of the 'quantity' k8s type: + https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery/pkg/api/resource/quantity.go + +* `requests` - + (Optional) + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is + explicitly specified, otherwise to an implementation-defined value. + The values of the map is string form of the 'quantity' k8s type: + https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery/pkg/api/resource/quantity.go + +The `metadata` block supports: + +* `labels` - + (Optional) + Map of string keys and values that can be used to organize and categorize + (scope and select) objects. May match selectors of replication controllers + and routes. + More info: http://kubernetes.io/docs/user-guide/labels + +* `generation` - + A sequence number representing a specific generation of the desired state. + +* `resource_version` - + An opaque value that represents the internal version of this object that + can be used by clients to determine when objects have changed. May be used + for optimistic concurrency, change detection, and the watch operation on a + resource or set of resources. They may only be valid for a + particular resource or set of resources. + More info: + https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency + +* `self_link` - + SelfLink is a URL representing this object. + +* `uid` - + UID is a unique id generated by the server on successful creation of a resource and is not + allowed to change on PUT operations. + More info: http://kubernetes.io/docs/user-guide/identifiers#uids + +* `namespace` - + (Optional) + In Cloud Run the namespace must be equal to either the + project ID or project number. + +* `annotations` - + (Optional) + Annotations is a key value map stored with a resource that + may be set by external tools to store and retrieve arbitrary metadata. More + info: http://kubernetes.io/docs/user-guide/annotations + +- - - + + +* `traffic` - + (Optional) + Traffic specifies how to distribute traffic over a collection of Knative Revisions + and Configurations Structure is documented below. + +* `template` - + (Optional) + template holds the latest specification for the Revision to + be stamped out. The template references the container image, and may also + include labels and annotations that should be attached to the Revision. + To correlate a Revision, and/or to force a Revision to be created when the + spec doesn't otherwise change, a nonce label may be provided in the + template metadata. For more details, see: + https://github.com/knative/serving/blob/master/docs/client-conventions.md#associate-modifications-with-revisions + Cloud Run does not currently support referencing a build that is + responsible for materializing the container image from source. 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. + + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + + +* `status` - + The current status of the Service. Structure is documented below. + + +The `status` block contains: + +* `conditions` - + Array of observed Service Conditions, indicating the current ready state of the service. Structure is documented below. + +* `url` - + From RouteStatus. URL holds the url that will distribute traffic over the provided traffic + targets. It generally has the form + https://{route-hash}-{project-hash}-{cluster-level-suffix}.a.run.app + +* `observed_generation` - + ObservedGeneration is the 'Generation' of the Route that was last processed by the + controller. + Clients polling for completed reconciliation should poll until observedGeneration = + metadata.generation and the Ready condition's status is True or False. + +* `latest_created_revision_name` - + From ConfigurationStatus. LatestCreatedRevisionName is the last revision that was created + from this Service's Configuration. It might not be ready yet, for that use + LatestReadyRevisionName. + +* `latest_ready_revision_name` - + From ConfigurationStatus. LatestReadyRevisionName holds the name of the latest Revision + stamped out from this Service's Configuration that has had its "Ready" condition become + "True". + + +The `conditions` block contains: + +* `message` - + Human readable message indicating details about the current status. + +* `status` - + Status of the condition, one of True, False, Unknown. + +* `reason` - + One-word CamelCase reason for the condition's current status. + +* `type` - + Type of domain mapping condition. + +## Timeouts + +This resource provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - Default is 4 minutes. +- `update` - Default is 4 minutes. +- `delete` - Default is 4 minutes. + +## Import + +Service can be imported using any of these accepted formats: + +``` +$ terraform import google_cloud_run_service.default locations/{{location}}/namespaces/{{project}}/services/{{name}} +$ terraform import google_cloud_run_service.default {{location}}/{{project}}/{{name}} +$ terraform import google_cloud_run_service.default {{location}}/{{name}} +``` + +-> 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. + +## User Project Overrides + +This resource supports [User Project Overrides](https://www.terraform.io/docs/providers/google/guides/provider_reference.html#user_project_override).