From dd9be82e3b3414de223b7b77e25c56410843074f Mon Sep 17 00:00:00 2001 From: The Magician Date: Wed, 22 Jul 2020 08:04:47 -0700 Subject: [PATCH] Monitoring metric descriptor (#3754) (#6829) * added metric descriptor resource and more functionality to PollAsync * added handrwitten test for metric descriptor * added a test case and cleaned up stale comments * fixed small formatting things * addressed more comments Co-authored-by: Tiffany Shen Signed-off-by: Modular Magician Co-authored-by: Tiffany Shen --- .changelog/3754.txt | 3 + google/common_polling.go | 81 ++- google/provider.go | 5 +- google/resource_app_engine_firewall_rule.go | 2 +- google/resource_big_query_job.go | 2 +- google/resource_cloud_run_domain_mapping.go | 2 +- google/resource_cloud_run_service.go | 4 +- google/resource_iap_brand.go | 2 +- .../resource_monitoring_metric_descriptor.go | 656 ++++++++++++++++++ ...toring_metric_descriptor_generated_test.go | 150 ++++ ...nitoring_metric_descriptor_sweeper_test.go | 124 ++++ ...ource_monitoring_metric_descriptor_test.go | 64 ++ google/resource_pubsub_subscription.go | 2 +- google/resource_pubsub_topic.go | 2 +- ...monitoring_metric_descriptor.html.markdown | 247 +++++++ website/google.erb | 4 + 16 files changed, 1337 insertions(+), 13 deletions(-) create mode 100644 .changelog/3754.txt create mode 100644 google/resource_monitoring_metric_descriptor.go create mode 100644 google/resource_monitoring_metric_descriptor_generated_test.go create mode 100644 google/resource_monitoring_metric_descriptor_sweeper_test.go create mode 100644 google/resource_monitoring_metric_descriptor_test.go create mode 100644 website/docs/r/monitoring_metric_descriptor.html.markdown diff --git a/.changelog/3754.txt b/.changelog/3754.txt new file mode 100644 index 00000000000..e8dd018430f --- /dev/null +++ b/.changelog/3754.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +`google_monitoring_metric_descriptor` +``` diff --git a/google/common_polling.go b/google/common_polling.go index e6468cc4b6c..181b4bb6550 100644 --- a/google/common_polling.go +++ b/google/common_polling.go @@ -3,6 +3,7 @@ package google import ( "fmt" "log" + "sync" "time" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" @@ -31,19 +32,81 @@ func SuccessPollResult() PollResult { return nil } -func PollingWaitTime(pollF PollReadFunc, checkResponse PollCheckResponseFunc, activity string, timeout time.Duration) error { +func PollingWaitTime(pollF PollReadFunc, checkResponse PollCheckResponseFunc, activity string, + timeout time.Duration, targetOccurrences int) error { log.Printf("[DEBUG] %s: Polling until expected state is read", activity) - return resource.Retry(timeout, func() *resource.RetryError { + log.Printf("[DEBUG] Target occurrences: %d", targetOccurrences) + if targetOccurrences == 1 { + return resource.Retry(timeout, func() *resource.RetryError { + readResp, readErr := pollF() + return checkResponse(readResp, readErr) + }) + } + return RetryWithTargetOccurrences(timeout, targetOccurrences, func() *resource.RetryError { readResp, readErr := pollF() return checkResponse(readResp, readErr) }) } +// RetryWithTargetOccurrences is a basic wrapper around StateChangeConf that will retry +// a function until it returns the specified amount of target occurrences continuously. +// Adapted from the Retry function in the go SDK. +func RetryWithTargetOccurrences(timeout time.Duration, targetOccurrences int, + f resource.RetryFunc) error { + // These are used to pull the error out of the function; need a mutex to + // avoid a data race. + var resultErr error + var resultErrMu sync.Mutex + + c := &resource.StateChangeConf{ + Pending: []string{"retryableerror"}, + Target: []string{"success"}, + Timeout: timeout, + MinTimeout: 500 * time.Millisecond, + ContinuousTargetOccurence: targetOccurrences, + Refresh: func() (interface{}, string, error) { + rerr := f() + + resultErrMu.Lock() + defer resultErrMu.Unlock() + + if rerr == nil { + resultErr = nil + return 42, "success", nil + } + + resultErr = rerr.Err + + if rerr.Retryable { + return 42, "retryableerror", nil + } + return nil, "quit", rerr.Err + }, + } + + _, waitErr := c.WaitForState() + + // Need to acquire the lock here to be able to avoid race using resultErr as + // the return value + resultErrMu.Lock() + defer resultErrMu.Unlock() + + // resultErr may be nil because the wait timed out and resultErr was never + // set; this is still an error + if resultErr == nil { + return waitErr + } + // resultErr takes precedence over waitErr if both are set because it is + // more likely to be useful + return resultErr +} + /** * Common PollCheckResponseFunc implementations */ -// PollCheckForExistence waits for a successful response, continues polling on 404, and returns any other error. +// PollCheckForExistence waits for a successful response, continues polling on 404, +// and returns any other error. func PollCheckForExistence(_ map[string]interface{}, respErr error) PollResult { if respErr != nil { if isGoogleApiErrorWithCode(respErr, 404) { @@ -53,3 +116,15 @@ func PollCheckForExistence(_ map[string]interface{}, respErr error) PollResult { } return SuccessPollResult() } + +// PollCheckForAbsence waits for a 404 response, continues polling on a successful +// response, and returns any other error. +func PollCheckForAbsence(_ map[string]interface{}, respErr error) PollResult { + if respErr != nil { + if isGoogleApiErrorWithCode(respErr, 404) { + return SuccessPollResult() + } + return ErrorPollResult(respErr) + } + return PendingStatusPollResult("found") +} diff --git a/google/provider.go b/google/provider.go index 473b776f791..69926cee11e 100644 --- a/google/provider.go +++ b/google/provider.go @@ -590,9 +590,9 @@ func Provider() terraform.ResourceProvider { return provider } -// Generated resources: 142 +// Generated resources: 143 // Generated IAM resources: 57 -// Total generated resources: 199 +// Total generated resources: 200 func ResourceMap() map[string]*schema.Resource { resourceMap, _ := ResourceMapWithErrors() return resourceMap @@ -761,6 +761,7 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { "google_monitoring_custom_service": resourceMonitoringService(), "google_monitoring_slo": resourceMonitoringSlo(), "google_monitoring_uptime_check_config": resourceMonitoringUptimeCheckConfig(), + "google_monitoring_metric_descriptor": resourceMonitoringMetricDescriptor(), "google_network_management_connectivity_test": resourceNetworkManagementConnectivityTest(), "google_os_config_patch_deployment": resourceOSConfigPatchDeployment(), "google_os_login_ssh_public_key": resourceOSLoginSSHPublicKey(), diff --git a/google/resource_app_engine_firewall_rule.go b/google/resource_app_engine_firewall_rule.go index 05106a6656e..e90029b5f9c 100644 --- a/google/resource_app_engine_firewall_rule.go +++ b/google/resource_app_engine_firewall_rule.go @@ -138,7 +138,7 @@ func resourceAppEngineFirewallRuleCreate(d *schema.ResourceData, meta interface{ } d.SetId(id) - err = PollingWaitTime(resourceAppEngineFirewallRulePollRead(d, meta), PollCheckForExistence, "Creating FirewallRule", d.Timeout(schema.TimeoutCreate)) + err = PollingWaitTime(resourceAppEngineFirewallRulePollRead(d, meta), PollCheckForExistence, "Creating FirewallRule", d.Timeout(schema.TimeoutCreate), 1) if err != nil { return fmt.Errorf("Error waiting to create FirewallRule: %s", err) } diff --git a/google/resource_big_query_job.go b/google/resource_big_query_job.go index 4ccfa1b0ecb..b46b4eb8344 100644 --- a/google/resource_big_query_job.go +++ b/google/resource_big_query_job.go @@ -900,7 +900,7 @@ func resourceBigQueryJobCreate(d *schema.ResourceData, meta interface{}) error { } d.SetId(id) - err = PollingWaitTime(resourceBigQueryJobPollRead(d, meta), PollCheckForExistence, "Creating Job", d.Timeout(schema.TimeoutCreate)) + err = PollingWaitTime(resourceBigQueryJobPollRead(d, meta), PollCheckForExistence, "Creating Job", d.Timeout(schema.TimeoutCreate), 1) if err != nil { return fmt.Errorf("Error waiting to create Job: %s", err) } diff --git a/google/resource_cloud_run_domain_mapping.go b/google/resource_cloud_run_domain_mapping.go index b28c7a1fd03..a94a0015459 100644 --- a/google/resource_cloud_run_domain_mapping.go +++ b/google/resource_cloud_run_domain_mapping.go @@ -293,7 +293,7 @@ func resourceCloudRunDomainMappingCreate(d *schema.ResourceData, meta interface{ } d.SetId(id) - err = PollingWaitTime(resourceCloudRunDomainMappingPollRead(d, meta), PollCheckKnativeStatus, "Creating DomainMapping", d.Timeout(schema.TimeoutCreate)) + err = PollingWaitTime(resourceCloudRunDomainMappingPollRead(d, meta), PollCheckKnativeStatus, "Creating DomainMapping", d.Timeout(schema.TimeoutCreate), 1) if err != nil { return fmt.Errorf("Error waiting to create DomainMapping: %s", err) } diff --git a/google/resource_cloud_run_service.go b/google/resource_cloud_run_service.go index 90bfea304ff..b7e1b5b0c73 100644 --- a/google/resource_cloud_run_service.go +++ b/google/resource_cloud_run_service.go @@ -643,7 +643,7 @@ func resourceCloudRunServiceCreate(d *schema.ResourceData, meta interface{}) err } d.SetId(id) - err = PollingWaitTime(resourceCloudRunServicePollRead(d, meta), PollCheckKnativeStatus, "Creating Service", d.Timeout(schema.TimeoutCreate)) + err = PollingWaitTime(resourceCloudRunServicePollRead(d, meta), PollCheckKnativeStatus, "Creating Service", d.Timeout(schema.TimeoutCreate), 1) if err != nil { return fmt.Errorf("Error waiting to create Service: %s", err) } @@ -785,7 +785,7 @@ func resourceCloudRunServiceUpdate(d *schema.ResourceData, meta interface{}) err return fmt.Errorf("Error updating Service %q: %s", d.Id(), err) } - err = PollingWaitTime(resourceCloudRunServicePollRead(d, meta), PollCheckKnativeStatus, "Updating Service", d.Timeout(schema.TimeoutUpdate)) + err = PollingWaitTime(resourceCloudRunServicePollRead(d, meta), PollCheckKnativeStatus, "Updating Service", d.Timeout(schema.TimeoutUpdate), 1) if err != nil { return err } diff --git a/google/resource_iap_brand.go b/google/resource_iap_brand.go index 14d54dc630a..b1d960e732a 100644 --- a/google/resource_iap_brand.go +++ b/google/resource_iap_brand.go @@ -121,7 +121,7 @@ func resourceIapBrandCreate(d *schema.ResourceData, meta interface{}) error { } d.SetId(id) - err = PollingWaitTime(resourceIapBrandPollRead(d, meta), PollCheckForExistence, "Creating Brand", d.Timeout(schema.TimeoutCreate)) + err = PollingWaitTime(resourceIapBrandPollRead(d, meta), PollCheckForExistence, "Creating Brand", d.Timeout(schema.TimeoutCreate), 1) if err != nil { return fmt.Errorf("Error waiting to create Brand: %s", err) } diff --git a/google/resource_monitoring_metric_descriptor.go b/google/resource_monitoring_metric_descriptor.go new file mode 100644 index 00000000000..9c890d56876 --- /dev/null +++ b/google/resource_monitoring_metric_descriptor.go @@ -0,0 +1,656 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "log" + "reflect" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func resourceMonitoringMetricDescriptor() *schema.Resource { + return &schema.Resource{ + Create: resourceMonitoringMetricDescriptorCreate, + Read: resourceMonitoringMetricDescriptorRead, + Update: resourceMonitoringMetricDescriptorUpdate, + Delete: resourceMonitoringMetricDescriptorDelete, + + Importer: &schema.ResourceImporter{ + State: resourceMonitoringMetricDescriptorImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(6 * time.Minute), + Update: schema.DefaultTimeout(6 * time.Minute), + Delete: schema.DefaultTimeout(6 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "description": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `A detailed description of the metric, which can be used in documentation.`, + }, + "display_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `A concise name for the metric, which can be displayed in user interfaces. Use sentence case without an ending period, for example "Request count".`, + }, + "metric_kind": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{"METRIC_KIND_UNSPECIFIED", "GAUGE", "DELTA", "CUMULATIVE"}, false), + Description: `Whether the metric records instantaneous values, changes to a value, etc. Some combinations of metricKind and valueType might not be supported. Possible values: ["METRIC_KIND_UNSPECIFIED", "GAUGE", "DELTA", "CUMULATIVE"]`, + }, + "type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The metric type, including its DNS name prefix. The type is not URL-encoded. All service defined metrics must be prefixed with the service name, in the format of {service name}/{relative metric name}, such as cloudsql.googleapis.com/database/cpu/utilization. The relative metric name must have only upper and lower-case letters, digits, '/' and underscores '_' are allowed. Additionally, the maximum number of characters allowed for the relative_metric_name is 100. All user-defined metric types have the DNS name custom.googleapis.com, external.googleapis.com, or logging.googleapis.com/user/.`, + }, + "value_type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{"BOOL", "INT64", "DOUBLE", "STRING", "DISTRIBUTION"}, false), + Description: `Whether the measurement is an integer, a floating-point number, etc. Some combinations of metricKind and valueType might not be supported. Possible values: ["BOOL", "INT64", "DOUBLE", "STRING", "DISTRIBUTION"]`, + }, + "labels": { + Type: schema.TypeSet, + Optional: true, + Description: `The set of labels that can be used to describe a specific instance of this metric type. In order to delete a label, the entire resource must be deleted, then created with the desired labels.`, + Elem: monitoringMetricDescriptorLabelsSchema(), + // Default schema.HashSchema is used. + }, + "launch_stage": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{"LAUNCH_STAGE_UNSPECIFIED", "UNIMPLEMENTED", "PRELAUNCH", "EARLY_ACCESS", "ALPHA", "BETA", "GA", "DEPRECATED", ""}, false), + Description: `The launch stage of the metric definition. Possible values: ["LAUNCH_STAGE_UNSPECIFIED", "UNIMPLEMENTED", "PRELAUNCH", "EARLY_ACCESS", "ALPHA", "BETA", "GA", "DEPRECATED"]`, + }, + "metadata": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Description: `Metadata which can be used to guide usage of the metric.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ingest_delay": { + Type: schema.TypeString, + Optional: true, + Description: `The delay of data points caused by ingestion. Data points older than this age are guaranteed to be ingested and available to be read, excluding data loss due to errors. In '[duration format](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf?&_ga=2.264881487.1507873253.1593446723-935052455.1591817775#google.protobuf.Duration)'.`, + AtLeastOneOf: []string{"metadata.0.sample_period", "metadata.0.ingest_delay"}, + }, + "sample_period": { + Type: schema.TypeString, + Optional: true, + Description: `The sampling period of metric data points. For metrics which are written periodically, consecutive data points are stored at this time interval, excluding data loss due to errors. Metrics with a higher granularity have a smaller sampling period. In '[duration format](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf?&_ga=2.264881487.1507873253.1593446723-935052455.1591817775#google.protobuf.Duration)'.`, + AtLeastOneOf: []string{"metadata.0.sample_period", "metadata.0.ingest_delay"}, + }, + }, + }, + }, + "unit": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `The units in which the metric value is reported. It is only applicable if the +valueType is INT64, DOUBLE, or DISTRIBUTION. The unit defines the representation of +the stored metric values. + +Different systems may scale the values to be more easily displayed (so a value of +0.02KBy might be displayed as 20By, and a value of 3523KBy might be displayed as +3.5MBy). However, if the unit is KBy, then the value of the metric is always in +thousands of bytes, no matter how it may be displayed. + +If you want a custom metric to record the exact number of CPU-seconds used by a job, +you can create an INT64 CUMULATIVE metric whose unit is s{CPU} (or equivalently +1s{CPU} or just s). If the job uses 12,005 CPU-seconds, then the value is written as +12005. + +Alternatively, if you want a custom metric to record data in a more granular way, you +can create a DOUBLE CUMULATIVE metric whose unit is ks{CPU}, and then write the value +12.005 (which is 12005/1000), or use Kis{CPU} and write 11.723 (which is 12005/1024). +The supported units are a subset of The Unified Code for Units of Measure standard. +More info can be found in the API documentation +(https://cloud.google.com/monitoring/api/ref_v3/rest/v3/projects.metricDescriptors).`, + }, + "monitored_resource_types": { + Type: schema.TypeList, + Computed: true, + Description: `If present, then a time series, which is identified partially by a metric type and a MonitoredResourceDescriptor, that is associated with this metric type can only be associated with one of the monitored resource types listed here. This field allows time series to be associated with the intersection of this metric type and the monitored resource types in this list.`, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: `The resource name of the metric descriptor.`, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + } +} + +func monitoringMetricDescriptorLabelsSchema() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + Description: `The key for this label. The key must not exceed 100 characters. The first character of the key must be an upper- or lower-case letter, the remaining characters must be letters, digits or underscores, and the key must match the regular expression [a-zA-Z][a-zA-Z0-9_]*`, + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: `A human-readable description for the label.`, + }, + "value_type": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"STRING", "BOOL", "INT64", ""}, false), + Description: `The type of data that can be assigned to the label. Default value: "STRING" Possible values: ["STRING", "BOOL", "INT64"]`, + Default: "STRING", + }, + }, + } +} + +func resourceMonitoringMetricDescriptorCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := make(map[string]interface{}) + typeProp, err := expandMonitoringMetricDescriptorType(d.Get("type"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("type"); !isEmptyValue(reflect.ValueOf(typeProp)) && (ok || !reflect.DeepEqual(v, typeProp)) { + obj["type"] = typeProp + } + labelsProp, err := expandMonitoringMetricDescriptorLabels(d.Get("labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("labels"); !isEmptyValue(reflect.ValueOf(labelsProp)) && (ok || !reflect.DeepEqual(v, labelsProp)) { + obj["labels"] = labelsProp + } + metricKindProp, err := expandMonitoringMetricDescriptorMetricKind(d.Get("metric_kind"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("metric_kind"); !isEmptyValue(reflect.ValueOf(metricKindProp)) && (ok || !reflect.DeepEqual(v, metricKindProp)) { + obj["metricKind"] = metricKindProp + } + valueTypeProp, err := expandMonitoringMetricDescriptorValueType(d.Get("value_type"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("value_type"); !isEmptyValue(reflect.ValueOf(valueTypeProp)) && (ok || !reflect.DeepEqual(v, valueTypeProp)) { + obj["valueType"] = valueTypeProp + } + unitProp, err := expandMonitoringMetricDescriptorUnit(d.Get("unit"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("unit"); !isEmptyValue(reflect.ValueOf(unitProp)) && (ok || !reflect.DeepEqual(v, unitProp)) { + obj["unit"] = unitProp + } + descriptionProp, err := expandMonitoringMetricDescriptorDescription(d.Get("description"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("description"); !isEmptyValue(reflect.ValueOf(descriptionProp)) && (ok || !reflect.DeepEqual(v, descriptionProp)) { + obj["description"] = descriptionProp + } + displayNameProp, err := expandMonitoringMetricDescriptorDisplayName(d.Get("display_name"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("display_name"); !isEmptyValue(reflect.ValueOf(displayNameProp)) && (ok || !reflect.DeepEqual(v, displayNameProp)) { + obj["displayName"] = displayNameProp + } + metadataProp, err := expandMonitoringMetricDescriptorMetadata(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 + } + launchStageProp, err := expandMonitoringMetricDescriptorLaunchStage(d.Get("launch_stage"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("launch_stage"); !isEmptyValue(reflect.ValueOf(launchStageProp)) && (ok || !reflect.DeepEqual(v, launchStageProp)) { + obj["launchStage"] = launchStageProp + } + + url, err := replaceVars(d, config, "{{MonitoringBasePath}}v3/projects/{{project}}/metricDescriptors") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new MetricDescriptor: %#v", obj) + project, err := getProject(d, config) + if err != nil { + return err + } + res, err := sendRequestWithTimeout(config, "POST", project, url, obj, d.Timeout(schema.TimeoutCreate), isMonitoringConcurrentEditError) + if err != nil { + return fmt.Errorf("Error creating MetricDescriptor: %s", err) + } + if err := d.Set("name", flattenMonitoringMetricDescriptorName(res["name"], d, config)); err != nil { + return fmt.Errorf(`Error setting computed identity field "name": %s`, err) + } + + // Store the ID now + id, err := replaceVars(d, config, "{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + err = PollingWaitTime(resourceMonitoringMetricDescriptorPollRead(d, meta), PollCheckForExistence, "Creating MetricDescriptor", d.Timeout(schema.TimeoutCreate), 20) + if err != nil { + return fmt.Errorf("Error waiting to create MetricDescriptor: %s", err) + } + + log.Printf("[DEBUG] Finished creating MetricDescriptor %q: %#v", d.Id(), res) + + return resourceMonitoringMetricDescriptorRead(d, meta) +} + +func resourceMonitoringMetricDescriptorPollRead(d *schema.ResourceData, meta interface{}) PollReadFunc { + return func() (map[string]interface{}, error) { + config := meta.(*Config) + + url, err := replaceVars(d, config, "{{MonitoringBasePath}}v3/{{name}}") + if err != nil { + return nil, err + } + + project, err := getProject(d, config) + if err != nil { + return nil, err + } + res, err := sendRequest(config, "GET", project, url, nil, isMonitoringConcurrentEditError) + if err != nil { + return res, err + } + return res, nil + } +} + +func resourceMonitoringMetricDescriptorRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + url, err := replaceVars(d, config, "{{MonitoringBasePath}}v3/{{name}}") + if err != nil { + return err + } + + project, err := getProject(d, config) + if err != nil { + return err + } + res, err := sendRequest(config, "GET", project, url, nil, isMonitoringConcurrentEditError) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("MonitoringMetricDescriptor %q", d.Id())) + } + + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error reading MetricDescriptor: %s", err) + } + + if err := d.Set("name", flattenMonitoringMetricDescriptorName(res["name"], d, config)); err != nil { + return fmt.Errorf("Error reading MetricDescriptor: %s", err) + } + if err := d.Set("type", flattenMonitoringMetricDescriptorType(res["type"], d, config)); err != nil { + return fmt.Errorf("Error reading MetricDescriptor: %s", err) + } + if err := d.Set("labels", flattenMonitoringMetricDescriptorLabels(res["labels"], d, config)); err != nil { + return fmt.Errorf("Error reading MetricDescriptor: %s", err) + } + if err := d.Set("metric_kind", flattenMonitoringMetricDescriptorMetricKind(res["metricKind"], d, config)); err != nil { + return fmt.Errorf("Error reading MetricDescriptor: %s", err) + } + if err := d.Set("value_type", flattenMonitoringMetricDescriptorValueType(res["valueType"], d, config)); err != nil { + return fmt.Errorf("Error reading MetricDescriptor: %s", err) + } + if err := d.Set("unit", flattenMonitoringMetricDescriptorUnit(res["unit"], d, config)); err != nil { + return fmt.Errorf("Error reading MetricDescriptor: %s", err) + } + if err := d.Set("description", flattenMonitoringMetricDescriptorDescription(res["description"], d, config)); err != nil { + return fmt.Errorf("Error reading MetricDescriptor: %s", err) + } + if err := d.Set("display_name", flattenMonitoringMetricDescriptorDisplayName(res["displayName"], d, config)); err != nil { + return fmt.Errorf("Error reading MetricDescriptor: %s", err) + } + if err := d.Set("monitored_resource_types", flattenMonitoringMetricDescriptorMonitoredResourceTypes(res["monitoredResourceTypes"], d, config)); err != nil { + return fmt.Errorf("Error reading MetricDescriptor: %s", err) + } + + return nil +} + +func resourceMonitoringMetricDescriptorUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + obj := make(map[string]interface{}) + typeProp, err := expandMonitoringMetricDescriptorType(d.Get("type"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("type"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, typeProp)) { + obj["type"] = typeProp + } + labelsProp, err := expandMonitoringMetricDescriptorLabels(d.Get("labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("labels"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, labelsProp)) { + obj["labels"] = labelsProp + } + metricKindProp, err := expandMonitoringMetricDescriptorMetricKind(d.Get("metric_kind"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("metric_kind"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, metricKindProp)) { + obj["metricKind"] = metricKindProp + } + valueTypeProp, err := expandMonitoringMetricDescriptorValueType(d.Get("value_type"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("value_type"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, valueTypeProp)) { + obj["valueType"] = valueTypeProp + } + unitProp, err := expandMonitoringMetricDescriptorUnit(d.Get("unit"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("unit"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, unitProp)) { + obj["unit"] = unitProp + } + descriptionProp, err := expandMonitoringMetricDescriptorDescription(d.Get("description"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("description"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, descriptionProp)) { + obj["description"] = descriptionProp + } + displayNameProp, err := expandMonitoringMetricDescriptorDisplayName(d.Get("display_name"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("display_name"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, displayNameProp)) { + obj["displayName"] = displayNameProp + } + metadataProp, err := expandMonitoringMetricDescriptorMetadata(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 + } + launchStageProp, err := expandMonitoringMetricDescriptorLaunchStage(d.Get("launch_stage"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("launch_stage"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, launchStageProp)) { + obj["launchStage"] = launchStageProp + } + + url, err := replaceVars(d, config, "{{MonitoringBasePath}}v3/projects/{{project}}/metricDescriptors") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating MetricDescriptor %q: %#v", d.Id(), obj) + _, err = sendRequestWithTimeout(config, "POST", project, url, obj, d.Timeout(schema.TimeoutUpdate), isMonitoringConcurrentEditError) + + if err != nil { + return fmt.Errorf("Error updating MetricDescriptor %q: %s", d.Id(), err) + } + + err = PollingWaitTime(resourceMonitoringMetricDescriptorPollRead(d, meta), PollCheckForExistence, "Updating MetricDescriptor", d.Timeout(schema.TimeoutUpdate), 20) + if err != nil { + return err + } + + return resourceMonitoringMetricDescriptorRead(d, meta) +} + +func resourceMonitoringMetricDescriptorDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{MonitoringBasePath}}v3/{{name}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting MetricDescriptor %q", d.Id()) + + res, err := sendRequestWithTimeout(config, "DELETE", project, url, obj, d.Timeout(schema.TimeoutDelete), isMonitoringConcurrentEditError) + if err != nil { + return handleNotFoundError(err, d, "MetricDescriptor") + } + + err = PollingWaitTime(resourceMonitoringMetricDescriptorPollRead(d, meta), PollCheckForAbsence, "Deleting MetricDescriptor", d.Timeout(schema.TimeoutCreate), 20) + if err != nil { + return fmt.Errorf("Error waiting to delete MetricDescriptor: %s", err) + } + + log.Printf("[DEBUG] Finished deleting MetricDescriptor %q: %#v", d.Id(), res) + return nil +} + +func resourceMonitoringMetricDescriptorImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + + config := meta.(*Config) + + // current import_formats can't import fields with forward slashes in their value + if err := parseImportId([]string{"(?P[^ ]+) (?P[^ ]+)", "(?P[^ ]+)"}, d, config); err != nil { + return nil, err + } + + return []*schema.ResourceData{d}, nil +} + +func flattenMonitoringMetricDescriptorName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenMonitoringMetricDescriptorType(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenMonitoringMetricDescriptorLabels(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return v + } + l := v.([]interface{}) + transformed := schema.NewSet(schema.HashResource(monitoringMetricDescriptorLabelsSchema()), []interface{}{}) + for _, raw := range l { + original := raw.(map[string]interface{}) + if len(original) < 1 { + // Do not include empty json objects coming back from the api + continue + } + transformed.Add(map[string]interface{}{ + "key": flattenMonitoringMetricDescriptorLabelsKey(original["key"], d, config), + "value_type": flattenMonitoringMetricDescriptorLabelsValueType(original["valueType"], d, config), + "description": flattenMonitoringMetricDescriptorLabelsDescription(original["description"], d, config), + }) + } + return transformed +} +func flattenMonitoringMetricDescriptorLabelsKey(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenMonitoringMetricDescriptorLabelsValueType(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil || isEmptyValue(reflect.ValueOf(v)) { + return "STRING" + } + + return v +} + +func flattenMonitoringMetricDescriptorLabelsDescription(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenMonitoringMetricDescriptorMetricKind(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenMonitoringMetricDescriptorValueType(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenMonitoringMetricDescriptorUnit(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenMonitoringMetricDescriptorDescription(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenMonitoringMetricDescriptorDisplayName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenMonitoringMetricDescriptorMonitoredResourceTypes(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func expandMonitoringMetricDescriptorType(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandMonitoringMetricDescriptorLabels(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + v = v.(*schema.Set).List() + l := v.([]interface{}) + req := make([]interface{}, 0, len(l)) + for _, raw := range l { + if raw == nil { + continue + } + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedKey, err := expandMonitoringMetricDescriptorLabelsKey(original["key"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedKey); val.IsValid() && !isEmptyValue(val) { + transformed["key"] = transformedKey + } + + transformedValueType, err := expandMonitoringMetricDescriptorLabelsValueType(original["value_type"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedValueType); val.IsValid() && !isEmptyValue(val) { + transformed["valueType"] = transformedValueType + } + + transformedDescription, err := expandMonitoringMetricDescriptorLabelsDescription(original["description"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedDescription); val.IsValid() && !isEmptyValue(val) { + transformed["description"] = transformedDescription + } + + req = append(req, transformed) + } + return req, nil +} + +func expandMonitoringMetricDescriptorLabelsKey(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandMonitoringMetricDescriptorLabelsValueType(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandMonitoringMetricDescriptorLabelsDescription(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandMonitoringMetricDescriptorMetricKind(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandMonitoringMetricDescriptorValueType(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandMonitoringMetricDescriptorUnit(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandMonitoringMetricDescriptorDescription(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandMonitoringMetricDescriptorDisplayName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandMonitoringMetricDescriptorMetadata(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{}) + + transformedSamplePeriod, err := expandMonitoringMetricDescriptorMetadataSamplePeriod(original["sample_period"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedSamplePeriod); val.IsValid() && !isEmptyValue(val) { + transformed["samplePeriod"] = transformedSamplePeriod + } + + transformedIngestDelay, err := expandMonitoringMetricDescriptorMetadataIngestDelay(original["ingest_delay"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedIngestDelay); val.IsValid() && !isEmptyValue(val) { + transformed["ingestDelay"] = transformedIngestDelay + } + + return transformed, nil +} + +func expandMonitoringMetricDescriptorMetadataSamplePeriod(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandMonitoringMetricDescriptorMetadataIngestDelay(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandMonitoringMetricDescriptorLaunchStage(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} diff --git a/google/resource_monitoring_metric_descriptor_generated_test.go b/google/resource_monitoring_metric_descriptor_generated_test.go new file mode 100644 index 00000000000..e4f6981ff29 --- /dev/null +++ b/google/resource_monitoring_metric_descriptor_generated_test.go @@ -0,0 +1,150 @@ +// ---------------------------------------------------------------------------- +// +// *** 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/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccMonitoringMetricDescriptor_monitoringMetricDescriptorBasicExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckMonitoringMetricDescriptorDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccMonitoringMetricDescriptor_monitoringMetricDescriptorBasicExample(context), + }, + { + ResourceName: "google_monitoring_metric_descriptor.basic", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"metadata", "launch_stage"}, + }, + }, + }) +} + +func testAccMonitoringMetricDescriptor_monitoringMetricDescriptorBasicExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_monitoring_metric_descriptor" "basic" { + description = "Daily sales records from all branch stores." + display_name = "Daily sales" + type = "custom.googleapis.com/stores/daily_sales" + metric_kind = "GAUGE" + value_type = "DOUBLE" + unit = "{USD}" + labels { + key = "store_id" + value_type = "STRING" + description = "The ID of the store." + } + launch_stage = "BETA" + metadata { + sample_period = "60s" + ingest_delay = "30s" + } +} +`, context) +} + +func TestAccMonitoringMetricDescriptor_monitoringMetricDescriptorAlertExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckMonitoringMetricDescriptorDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccMonitoringMetricDescriptor_monitoringMetricDescriptorAlertExample(context), + }, + { + ResourceName: "google_monitoring_metric_descriptor.with_alert", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"metadata", "launch_stage"}, + }, + }, + }) +} + +func testAccMonitoringMetricDescriptor_monitoringMetricDescriptorAlertExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_monitoring_metric_descriptor" "with_alert" { + description = "Daily sales records from all branch stores." + display_name = "Daily sales" + type = "custom.googleapis.com/stores/daily_sales" + metric_kind = "GAUGE" + value_type = "DOUBLE" + unit = "{USD}" +} + +resource "google_monitoring_alert_policy" "alert_policy" { + display_name = "Alert on daily sales" + combiner = "OR" + conditions { + display_name = "test condition" + condition_threshold { + filter = "metric.type=\"${google_monitoring_metric_descriptor.with_alert.type}\" AND resource.type=\"gce_instance\"" + duration = "60s" + comparison = "COMPARISON_GT" + } + } +} +`, context) +} + +func testAccCheckMonitoringMetricDescriptorDestroyProducer(t *testing.T) func(s *terraform.State) error { + return func(s *terraform.State) error { + for name, rs := range s.RootModule().Resources { + if rs.Type != "google_monitoring_metric_descriptor" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := googleProviderConfig(t) + + url, err := replaceVarsForTest(config, rs, "{{MonitoringBasePath}}v3/{{name}}") + if err != nil { + return err + } + + _, err = sendRequest(config, "GET", "", url, nil, isMonitoringConcurrentEditError) + if err == nil { + return fmt.Errorf("MonitoringMetricDescriptor still exists at %s", url) + } + } + + return nil + } +} diff --git a/google/resource_monitoring_metric_descriptor_sweeper_test.go b/google/resource_monitoring_metric_descriptor_sweeper_test.go new file mode 100644 index 00000000000..e3cbaf4ff21 --- /dev/null +++ b/google/resource_monitoring_metric_descriptor_sweeper_test.go @@ -0,0 +1,124 @@ +// ---------------------------------------------------------------------------- +// +// *** 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 ( + "context" + "log" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func init() { + resource.AddTestSweepers("MonitoringMetricDescriptor", &resource.Sweeper{ + Name: "MonitoringMetricDescriptor", + F: testSweepMonitoringMetricDescriptor, + }) +} + +// At the time of writing, the CI only passes us-central1 as the region +func testSweepMonitoringMetricDescriptor(region string) error { + resourceName := "MonitoringMetricDescriptor" + log.Printf("[INFO][SWEEPER_LOG] Starting sweeper for %s", resourceName) + + config, err := sharedConfigForRegion(region) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error getting shared config for region: %s", err) + return err + } + + err = config.LoadAndValidate(context.Background()) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error loading: %s", err) + return err + } + + t := &testing.T{} + billingId := getTestBillingAccountFromEnv(t) + + // Setup variables to replace in list template + d := &ResourceDataMock{ + FieldsInSchema: map[string]interface{}{ + "project": config.Project, + "region": region, + "location": region, + "zone": "-", + "billing_account": billingId, + }, + } + + listTemplate := strings.Split("https://monitoring.googleapis.com/v3/projects/{{project}}/metricDescriptors", "?")[0] + listUrl, err := replaceVars(d, config, listTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing sweeper list url: %s", err) + return nil + } + + res, err := sendRequest(config, "GET", config.Project, listUrl, nil) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error in response from request %s: %s", listUrl, err) + return nil + } + + resourceList, ok := res["metricDescriptors"] + if !ok { + log.Printf("[INFO][SWEEPER_LOG] Nothing found in response.") + return nil + } + + rl := resourceList.([]interface{}) + + log.Printf("[INFO][SWEEPER_LOG] Found %d items in %s list response.", len(rl), resourceName) + // Keep count of items that aren't sweepable for logging. + nonPrefixCount := 0 + for _, ri := range rl { + obj := ri.(map[string]interface{}) + if obj["name"] == nil { + log.Printf("[INFO][SWEEPER_LOG] %s resource name was nil", resourceName) + return nil + } + + name := GetResourceNameFromSelfLink(obj["name"].(string)) + // Skip resources that shouldn't be sweeped + if !isSweepableTestResource(name) { + nonPrefixCount++ + continue + } + + deleteTemplate := "https://monitoring.googleapis.com/v3/{{name}}" + deleteUrl, err := replaceVars(d, config, deleteTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing delete url: %s", err) + return nil + } + deleteUrl = deleteUrl + name + + // Don't wait on operations as we may have a lot to delete + _, err = sendRequest(config, "DELETE", config.Project, deleteUrl, nil) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error deleting for url %s : %s", deleteUrl, err) + } else { + log.Printf("[INFO][SWEEPER_LOG] Sent delete request for %s resource: %s", resourceName, name) + } + } + + if nonPrefixCount > 0 { + log.Printf("[INFO][SWEEPER_LOG] %d items were non-sweepable and skipped.", nonPrefixCount) + } + + return nil +} diff --git a/google/resource_monitoring_metric_descriptor_test.go b/google/resource_monitoring_metric_descriptor_test.go new file mode 100644 index 00000000000..34375651ffb --- /dev/null +++ b/google/resource_monitoring_metric_descriptor_test.go @@ -0,0 +1,64 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccMonitoringMetricDescriptor_update(t *testing.T) { + t.Parallel() + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckMonitoringMetricDescriptorDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccMonitoringMetricDescriptor_update("key1", "STRING", + "description1", "30s", "30s"), + }, + { + ResourceName: "google_monitoring_metric_descriptor.basic", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"metadata", "launch_stage"}, + }, + { + Config: testAccMonitoringMetricDescriptor_update("key2", "INT64", + "description2", "60s", "60s"), + }, + { + ResourceName: "google_monitoring_metric_descriptor.basic", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"metadata", "launch_stage"}, + }, + }, + }) +} + +func testAccMonitoringMetricDescriptor_update(key, valueType, description, + samplePeriod, ingestDelay string) string { + return fmt.Sprintf(` +resource "google_monitoring_metric_descriptor" "basic" { + description = "Daily sales records from all branch stores." + display_name = "daily sales" + type = "custom.googleapis.com/stores/daily_sales" + metric_kind = "GAUGE" + value_type = "DOUBLE" + unit = "{USD}" + labels { + key = "%s" + value_type = "%s" + description = "%s" + } + launch_stage = "BETA" + metadata { + sample_period = "%s" + ingest_delay = "%s" + } +} +`, key, valueType, description, samplePeriod, ingestDelay, + ) +} diff --git a/google/resource_pubsub_subscription.go b/google/resource_pubsub_subscription.go index 60a15097b2f..fbecfd0e57a 100644 --- a/google/resource_pubsub_subscription.go +++ b/google/resource_pubsub_subscription.go @@ -372,7 +372,7 @@ func resourcePubsubSubscriptionCreate(d *schema.ResourceData, meta interface{}) } d.SetId(id) - err = PollingWaitTime(resourcePubsubSubscriptionPollRead(d, meta), PollCheckForExistence, "Creating Subscription", d.Timeout(schema.TimeoutCreate)) + err = PollingWaitTime(resourcePubsubSubscriptionPollRead(d, meta), PollCheckForExistence, "Creating Subscription", d.Timeout(schema.TimeoutCreate), 1) if err != nil { log.Printf("[ERROR] Unable to confirm eventually consistent Subscription %q finished updating: %q", d.Id(), err) } diff --git a/google/resource_pubsub_topic.go b/google/resource_pubsub_topic.go index e2757163622..e446a8cbaa7 100644 --- a/google/resource_pubsub_topic.go +++ b/google/resource_pubsub_topic.go @@ -157,7 +157,7 @@ func resourcePubsubTopicCreate(d *schema.ResourceData, meta interface{}) error { } d.SetId(id) - err = PollingWaitTime(resourcePubsubTopicPollRead(d, meta), PollCheckForExistence, "Creating Topic", d.Timeout(schema.TimeoutCreate)) + err = PollingWaitTime(resourcePubsubTopicPollRead(d, meta), PollCheckForExistence, "Creating Topic", d.Timeout(schema.TimeoutCreate), 1) if err != nil { log.Printf("[ERROR] Unable to confirm eventually consistent Topic %q finished updating: %q", d.Id(), err) } diff --git a/website/docs/r/monitoring_metric_descriptor.html.markdown b/website/docs/r/monitoring_metric_descriptor.html.markdown new file mode 100644 index 00000000000..3c52a8db031 --- /dev/null +++ b/website/docs/r/monitoring_metric_descriptor.html.markdown @@ -0,0 +1,247 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** 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 (Stackdriver) Monitoring" +layout: "google" +page_title: "Google: google_monitoring_metric_descriptor" +sidebar_current: "docs-google-monitoring-metric-descriptor" +description: |- + Defines a metric type and its schema. +--- + +# google\_monitoring\_metric\_descriptor + +Defines a metric type and its schema. Once a metric descriptor is created, deleting or altering it stops data collection and makes the metric type's existing data unusable. + + +To get more information about MetricDescriptor, see: + +* [API documentation](https://cloud.google.com/monitoring/api/ref_v3/rest/v3/projects.metricDescriptors) +* How-to Guides + * [Official Documentation](https://cloud.google.com/monitoring/custom-metrics/) + + +## Example Usage - Monitoring Metric Descriptor Basic + + +```hcl +resource "google_monitoring_metric_descriptor" "basic" { + description = "Daily sales records from all branch stores." + display_name = "Daily sales" + type = "custom.googleapis.com/stores/daily_sales" + metric_kind = "GAUGE" + value_type = "DOUBLE" + unit = "{USD}" + labels { + key = "store_id" + value_type = "STRING" + description = "The ID of the store." + } + launch_stage = "BETA" + metadata { + sample_period = "60s" + ingest_delay = "30s" + } +} +``` + +## Example Usage - Monitoring Metric Descriptor Alert + + +```hcl +resource "google_monitoring_metric_descriptor" "with_alert" { + description = "Daily sales records from all branch stores." + display_name = "Daily sales" + type = "custom.googleapis.com/stores/daily_sales" + metric_kind = "GAUGE" + value_type = "DOUBLE" + unit = "{USD}" +} + +resource "google_monitoring_alert_policy" "alert_policy" { + display_name = "Alert on daily sales" + combiner = "OR" + conditions { + display_name = "test condition" + condition_threshold { + filter = "metric.type=\"${google_monitoring_metric_descriptor.with_alert.type}\" AND resource.type=\"gce_instance\"" + duration = "60s" + comparison = "COMPARISON_GT" + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `type` - + (Required) + The metric type, including its DNS name prefix. The type is not URL-encoded. All service defined metrics must be prefixed with the service name, in the format of {service name}/{relative metric name}, such as cloudsql.googleapis.com/database/cpu/utilization. The relative metric name must have only upper and lower-case letters, digits, '/' and underscores '_' are allowed. Additionally, the maximum number of characters allowed for the relative_metric_name is 100. All user-defined metric types have the DNS name custom.googleapis.com, external.googleapis.com, or logging.googleapis.com/user/. + +* `metric_kind` - + (Required) + Whether the metric records instantaneous values, changes to a value, etc. Some combinations of metricKind and valueType might not be supported. + + Possible values are: + * `METRIC_KIND_UNSPECIFIED` + * `GAUGE` + * `DELTA` + * `CUMULATIVE` + +* `value_type` - + (Required) + Whether the measurement is an integer, a floating-point number, etc. Some combinations of metricKind and valueType might not be supported. + + Possible values are: + * `BOOL` + * `INT64` + * `DOUBLE` + * `STRING` + * `DISTRIBUTION` + +* `description` - + (Required) + A detailed description of the metric, which can be used in documentation. + +* `display_name` - + (Required) + A concise name for the metric, which can be displayed in user interfaces. Use sentence case without an ending period, for example "Request count". + + +- - - + + +* `labels` - + (Optional) + The set of labels that can be used to describe a specific instance of this metric type. In order to delete a label, the entire resource must be deleted, then created with the desired labels. Structure is documented below. + +* `unit` - + (Optional) + The units in which the metric value is reported. It is only applicable if the + valueType is INT64, DOUBLE, or DISTRIBUTION. The unit defines the representation of + the stored metric values. + Different systems may scale the values to be more easily displayed (so a value of + 0.02KBy might be displayed as 20By, and a value of 3523KBy might be displayed as + 3.5MBy). However, if the unit is KBy, then the value of the metric is always in + thousands of bytes, no matter how it may be displayed. + If you want a custom metric to record the exact number of CPU-seconds used by a job, + you can create an INT64 CUMULATIVE metric whose unit is s{CPU} (or equivalently + 1s{CPU} or just s). If the job uses 12,005 CPU-seconds, then the value is written as + 12005. + Alternatively, if you want a custom metric to record data in a more granular way, you + can create a DOUBLE CUMULATIVE metric whose unit is ks{CPU}, and then write the value + 12.005 (which is 12005/1000), or use Kis{CPU} and write 11.723 (which is 12005/1024). + The supported units are a subset of The Unified Code for Units of Measure standard. + More info can be found in the API documentation + (https://cloud.google.com/monitoring/api/ref_v3/rest/v3/projects.metricDescriptors). + +* `metadata` - + (Optional) + Metadata which can be used to guide usage of the metric. Structure is documented below. + +* `launch_stage` - + (Optional) + The launch stage of the metric definition. + + Possible values are: + * `LAUNCH_STAGE_UNSPECIFIED` + * `UNIMPLEMENTED` + * `PRELAUNCH` + * `EARLY_ACCESS` + * `ALPHA` + * `BETA` + * `GA` + * `DEPRECATED` + +* `project` - (Optional) The ID of the project in which the resource belongs. + If it is not provided, the provider project is used. + + +The `labels` block supports: + +* `key` - + (Required) + The key for this label. The key must not exceed 100 characters. The first character of the key must be an upper- or lower-case letter, the remaining characters must be letters, digits or underscores, and the key must match the regular expression [a-zA-Z][a-zA-Z0-9_]* + +* `value_type` - + (Optional) + The type of data that can be assigned to the label. + + Default value: `STRING` + Possible values are: + * `STRING` + * `BOOL` + * `INT64` + +* `description` - + (Optional) + A human-readable description for the label. + +The `metadata` block supports: + +* `sample_period` - + (Optional) + The sampling period of metric data points. For metrics which are written periodically, consecutive data points are stored at this time interval, excluding data loss due to errors. Metrics with a higher granularity have a smaller sampling period. In `[duration format](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf?&_ga=2.264881487.1507873253.1593446723-935052455.1591817775#google.protobuf.Duration)`. + +* `ingest_delay` - + (Optional) + The delay of data points caused by ingestion. Data points older than this age are guaranteed to be ingested and available to be read, excluding data loss due to errors. In `[duration format](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf?&_ga=2.264881487.1507873253.1593446723-935052455.1591817775#google.protobuf.Duration)`. + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `id` - an identifier for the resource with format `{{name}}` + +* `name` - + The resource name of the metric descriptor. + +* `monitored_resource_types` - + If present, then a time series, which is identified partially by a metric type and a MonitoredResourceDescriptor, that is associated with this metric type can only be associated with one of the monitored resource types listed here. This field allows time series to be associated with the intersection of this metric type and the monitored resource types in this list. + + +## Timeouts + +This resource provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - Default is 6 minutes. +- `update` - Default is 6 minutes. +- `delete` - Default is 6 minutes. + +## Import + +MetricDescriptor can be imported using any of these accepted formats: + +``` +$ terraform import google_monitoring_metric_descriptor.default {{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/google.erb b/website/google.erb index a56d0f8d9b1..a0b60486dab 100644 --- a/website/google.erb +++ b/website/google.erb @@ -291,6 +291,10 @@ google_monitoring_group +
  • + google_monitoring_metric_descriptor +
  • +
  • google_monitoring_notification_channel