diff --git a/.changelog/6604.txt b/.changelog/6604.txt new file mode 100644 index 00000000000..a829395c848 --- /dev/null +++ b/.changelog/6604.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +google_monitoring_generic_service +``` diff --git a/google/provider.go b/google/provider.go index 1a6c4312a74..d4fdc1b1420 100644 --- a/google/provider.go +++ b/google/provider.go @@ -900,9 +900,9 @@ func Provider() *schema.Provider { return provider } -// Generated resources: 236 +// Generated resources: 237 // Generated IAM resources: 138 -// Total generated resources: 374 +// Total generated resources: 375 func ResourceMap() map[string]*schema.Resource { resourceMap, _ := ResourceMapWithErrors() return resourceMap @@ -1196,6 +1196,7 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { "google_monitoring_group": resourceMonitoringGroup(), "google_monitoring_notification_channel": resourceMonitoringNotificationChannel(), "google_monitoring_custom_service": resourceMonitoringService(), + "google_monitoring_service": resourceMonitoringGenericService(), "google_monitoring_slo": resourceMonitoringSlo(), "google_monitoring_uptime_check_config": resourceMonitoringUptimeCheckConfig(), "google_monitoring_metric_descriptor": resourceMonitoringMetricDescriptor(), diff --git a/google/resource_monitoring_service.go b/google/resource_monitoring_service.go new file mode 100644 index 00000000000..f0079ffc4fd --- /dev/null +++ b/google/resource_monitoring_service.go @@ -0,0 +1,475 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "log" + "reflect" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceMonitoringGenericService() *schema.Resource { + return &schema.Resource{ + Create: resourceMonitoringGenericServiceCreate, + Read: resourceMonitoringGenericServiceRead, + Update: resourceMonitoringGenericServiceUpdate, + Delete: resourceMonitoringGenericServiceDelete, + + Importer: &schema.ResourceImporter{ + State: resourceMonitoringGenericServiceImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(20 * time.Minute), + Update: schema.DefaultTimeout(20 * time.Minute), + Delete: schema.DefaultTimeout(20 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "service_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `An optional service ID to use. If not given, the server will generate a +service ID.`, + }, + "basic_service": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Description: `A well-known service type, defined by its service type and service labels.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "service_labels": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + Description: `Labels that specify the resource that emits the monitoring data +which is used for SLO reporting of this 'Service'.`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "service_type": { + Type: schema.TypeString, + Optional: true, + Description: `The type of service that this basic service defines, e.g. +APP_ENGINE service type`, + }, + }, + }, + }, + "display_name": { + Type: schema.TypeString, + Optional: true, + Description: `Name used for UI elements listing this Service.`, + }, + "user_labels": { + Type: schema.TypeMap, + Optional: true, + Description: `Labels which have been used to annotate the service. Label keys must start +with a letter. Label keys and values may contain lowercase letters, +numbers, underscores, and dashes. Label keys and values have a maximum +length of 63 characters, and must be less than 128 bytes in size. Up to 64 +label entries may be stored. For labels which do not have a semantic value, +the empty string may be supplied for the label value.`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: `The full resource name for this service. The syntax is: +projects/[PROJECT_ID]/services/[SERVICE_ID].`, + }, + "telemetry": { + Type: schema.TypeList, + Computed: true, + Description: `Configuration for how to query telemetry on a Service.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "resource_name": { + Type: schema.TypeString, + Optional: true, + Description: `The full name of the resource that defines this service. +Formatted as described in +https://cloud.google.com/apis/design/resource_names.`, + }, + }, + }, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + UseJSONNumber: true, + } +} + +func resourceMonitoringGenericServiceCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + obj := make(map[string]interface{}) + displayNameProp, err := expandMonitoringGenericServiceDisplayName(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 + } + userLabelsProp, err := expandMonitoringGenericServiceUserLabels(d.Get("user_labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("user_labels"); ok || !reflect.DeepEqual(v, userLabelsProp) { + obj["userLabels"] = userLabelsProp + } + basicServiceProp, err := expandMonitoringGenericServiceBasicService(d.Get("basic_service"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("basic_service"); !isEmptyValue(reflect.ValueOf(basicServiceProp)) && (ok || !reflect.DeepEqual(v, basicServiceProp)) { + obj["basicService"] = basicServiceProp + } + + url, err := replaceVars(d, config, "{{MonitoringBasePath}}v3/projects/{{project}}/services?serviceId={{service_id}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new GenericService: %#v", obj) + billingProject := "" + + project, err := getProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for GenericService: %s", err) + } + billingProject = project + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequestWithTimeout(config, "POST", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutCreate), isMonitoringConcurrentEditError) + if err != nil { + return fmt.Errorf("Error creating GenericService: %s", err) + } + if err := d.Set("name", flattenMonitoringGenericServiceName(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, "projects/{{project}}/services/{{service_id}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating GenericService %q: %#v", d.Id(), res) + + return resourceMonitoringGenericServiceRead(d, meta) +} + +func resourceMonitoringGenericServiceRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{MonitoringBasePath}}v3/projects/{{project}}/services/{{service_id}}") + if err != nil { + return err + } + + billingProject := "" + + project, err := getProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for GenericService: %s", err) + } + billingProject = project + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequest(config, "GET", billingProject, url, userAgent, nil, isMonitoringConcurrentEditError) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("MonitoringGenericService %q", d.Id())) + } + + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error reading GenericService: %s", err) + } + + if err := d.Set("name", flattenMonitoringGenericServiceName(res["name"], d, config)); err != nil { + return fmt.Errorf("Error reading GenericService: %s", err) + } + if err := d.Set("display_name", flattenMonitoringGenericServiceDisplayName(res["displayName"], d, config)); err != nil { + return fmt.Errorf("Error reading GenericService: %s", err) + } + if err := d.Set("user_labels", flattenMonitoringGenericServiceUserLabels(res["userLabels"], d, config)); err != nil { + return fmt.Errorf("Error reading GenericService: %s", err) + } + if err := d.Set("telemetry", flattenMonitoringGenericServiceTelemetry(res["telemetry"], d, config)); err != nil { + return fmt.Errorf("Error reading GenericService: %s", err) + } + if err := d.Set("basic_service", flattenMonitoringGenericServiceBasicService(res["basicService"], d, config)); err != nil { + return fmt.Errorf("Error reading GenericService: %s", err) + } + + return nil +} + +func resourceMonitoringGenericServiceUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + billingProject := "" + + project, err := getProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for GenericService: %s", err) + } + billingProject = project + + obj := make(map[string]interface{}) + displayNameProp, err := expandMonitoringGenericServiceDisplayName(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 + } + userLabelsProp, err := expandMonitoringGenericServiceUserLabels(d.Get("user_labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("user_labels"); ok || !reflect.DeepEqual(v, userLabelsProp) { + obj["userLabels"] = userLabelsProp + } + + url, err := replaceVars(d, config, "{{MonitoringBasePath}}v3/projects/{{project}}/services/{{service_id}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating GenericService %q: %#v", d.Id(), obj) + updateMask := []string{} + + if d.HasChange("display_name") { + updateMask = append(updateMask, "displayName") + } + + if d.HasChange("user_labels") { + updateMask = append(updateMask, "userLabels") + } + // updateMask is a URL parameter but not present in the schema, so replaceVars + // won't set it + url, err = addQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")}) + if err != nil { + return err + } + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequestWithTimeout(config, "PATCH", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutUpdate), isMonitoringConcurrentEditError) + + if err != nil { + return fmt.Errorf("Error updating GenericService %q: %s", d.Id(), err) + } else { + log.Printf("[DEBUG] Finished updating GenericService %q: %#v", d.Id(), res) + } + + return resourceMonitoringGenericServiceRead(d, meta) +} + +func resourceMonitoringGenericServiceDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + billingProject := "" + + project, err := getProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for GenericService: %s", err) + } + billingProject = project + + url, err := replaceVars(d, config, "{{MonitoringBasePath}}v3/projects/{{project}}/services/{{service_id}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting GenericService %q", d.Id()) + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequestWithTimeout(config, "DELETE", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutDelete), isMonitoringConcurrentEditError) + if err != nil { + return handleNotFoundError(err, d, "GenericService") + } + + log.Printf("[DEBUG] Finished deleting GenericService %q: %#v", d.Id(), res) + return nil +} + +func resourceMonitoringGenericServiceImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + if err := parseImportId([]string{ + "projects/(?P[^/]+)/services/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)", + "(?P[^/]+)", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := replaceVars(d, config, "projects/{{project}}/services/{{service_id}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenMonitoringGenericServiceName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenMonitoringGenericServiceDisplayName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenMonitoringGenericServiceUserLabels(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenMonitoringGenericServiceTelemetry(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["resource_name"] = + flattenMonitoringGenericServiceTelemetryResourceName(original["resourceName"], d, config) + return []interface{}{transformed} +} +func flattenMonitoringGenericServiceTelemetryResourceName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenMonitoringGenericServiceBasicService(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["service_type"] = + flattenMonitoringGenericServiceBasicServiceServiceType(original["serviceType"], d, config) + transformed["service_labels"] = + flattenMonitoringGenericServiceBasicServiceServiceLabels(original["serviceLabels"], d, config) + return []interface{}{transformed} +} +func flattenMonitoringGenericServiceBasicServiceServiceType(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenMonitoringGenericServiceBasicServiceServiceLabels(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func expandMonitoringGenericServiceDisplayName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandMonitoringGenericServiceUserLabels(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 expandMonitoringGenericServiceBasicService(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{}) + + transformedServiceType, err := expandMonitoringGenericServiceBasicServiceServiceType(original["service_type"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedServiceType); val.IsValid() && !isEmptyValue(val) { + transformed["serviceType"] = transformedServiceType + } + + transformedServiceLabels, err := expandMonitoringGenericServiceBasicServiceServiceLabels(original["service_labels"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedServiceLabels); val.IsValid() && !isEmptyValue(val) { + transformed["serviceLabels"] = transformedServiceLabels + } + + return transformed, nil +} + +func expandMonitoringGenericServiceBasicServiceServiceType(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandMonitoringGenericServiceBasicServiceServiceLabels(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 +} diff --git a/google/resource_monitoring_service_generated_test.go b/google/resource_monitoring_service_generated_test.go new file mode 100644 index 00000000000..2e636531cbb --- /dev/null +++ b/google/resource_monitoring_service_generated_test.go @@ -0,0 +1,103 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccMonitoringGenericService_monitoringServiceExampleExample(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: testAccCheckMonitoringGenericServiceDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccMonitoringGenericService_monitoringServiceExampleExample(context), + }, + { + ResourceName: "google_monitoring_service.my_service", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"service_id"}, + }, + }, + }) +} + +func testAccMonitoringGenericService_monitoringServiceExampleExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_monitoring_service" "my_service" { + service_id = "tf-test-my-service%{random_suffix}" + display_name = "My Service tf-test-my-service%{random_suffix}" + + user_labels = { + my_key = "my_value" + my_other_key = "my_other_value" + } + + basic_service { + service_type = "APP_ENGINE" + service_labels = { + module_id = "another-module-id" + } + } +} +`, context) +} + +func testAccCheckMonitoringGenericServiceDestroyProducer(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_service" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := googleProviderConfig(t) + + url, err := replaceVarsForTest(config, rs, "{{MonitoringBasePath}}v3/projects/{{project}}/services/{{service_id}}") + if err != nil { + return err + } + + billingProject := "" + + if config.BillingProject != "" { + billingProject = config.BillingProject + } + + _, err = sendRequest(config, "GET", billingProject, url, config.userAgent, nil, isMonitoringConcurrentEditError) + if err == nil { + return fmt.Errorf("MonitoringGenericService still exists at %s", url) + } + } + + return nil + } +} diff --git a/google/resource_monitoring_service_sweeper_test.go b/google/resource_monitoring_service_sweeper_test.go new file mode 100644 index 00000000000..04dda826d9d --- /dev/null +++ b/google/resource_monitoring_service_sweeper_test.go @@ -0,0 +1,128 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "context" + "log" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func init() { + resource.AddTestSweepers("MonitoringGenericService", &resource.Sweeper{ + Name: "MonitoringGenericService", + F: testSweepMonitoringGenericService, + }) +} + +// At the time of writing, the CI only passes us-central1 as the region +func testSweepMonitoringGenericService(region string) error { + resourceName := "MonitoringGenericService" + 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}}/services", "?")[0] + listUrl, err := replaceVars(d, config, listTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing sweeper list url: %s", err) + return nil + } + + res, err := sendRequest(config, "GET", config.Project, listUrl, config.userAgent, nil) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error in response from request %s: %s", listUrl, err) + return nil + } + + resourceList, ok := res["genericServices"] + if !ok { + log.Printf("[INFO][SWEEPER_LOG] Nothing found in response.") + return nil + } + + rl := resourceList.([]interface{}) + + log.Printf("[INFO][SWEEPER_LOG] Found %d items in %s list response.", len(rl), resourceName) + // Keep count of items that aren't sweepable for logging. + nonPrefixCount := 0 + for _, ri := range rl { + obj := ri.(map[string]interface{}) + var name string + // Id detected in the delete URL, attempt to use id. + if obj["id"] != nil { + name = GetResourceNameFromSelfLink(obj["id"].(string)) + } else if obj["name"] != nil { + name = GetResourceNameFromSelfLink(obj["name"].(string)) + } else { + log.Printf("[INFO][SWEEPER_LOG] %s resource name and id were nil", resourceName) + return nil + } + // Skip resources that shouldn't be sweeped + if !isSweepableTestResource(name) { + nonPrefixCount++ + continue + } + + deleteTemplate := "https://monitoring.googleapis.com/v3/projects/{{project}}/services/{{service_id}}" + deleteUrl, err := replaceVars(d, config, deleteTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing delete url: %s", err) + return nil + } + deleteUrl = deleteUrl + name + + // Don't wait on operations as we may have a lot to delete + _, err = sendRequest(config, "DELETE", config.Project, deleteUrl, config.userAgent, nil) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error deleting for url %s : %s", deleteUrl, err) + } else { + log.Printf("[INFO][SWEEPER_LOG] Sent delete request for %s resource: %s", resourceName, name) + } + } + + if nonPrefixCount > 0 { + log.Printf("[INFO][SWEEPER_LOG] %d items were non-sweepable and skipped.", nonPrefixCount) + } + + return nil +} diff --git a/google/resource_monitoring_service_test.go b/google/resource_monitoring_service_test.go new file mode 100644 index 00000000000..59096315d9b --- /dev/null +++ b/google/resource_monitoring_service_test.go @@ -0,0 +1,52 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccMonitoringService_basic(t *testing.T) { + t.Parallel() + + randomSuffix := randString(t, 10) + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckMonitoringServiceDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccMonitoringSlo_cloudEndpoints(randomSuffix, "an-endpoint"), + }, + { + ResourceName: "google_monitoring_service.srv", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccMonitoringSlo_cloudEndpoints(randomSuffix, "another-endpoint"), + }, + { + ResourceName: "google_monitoring_service.srv", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccMonitoringSlo_cloudEndpoints(randSuffix, endpoint string) string { + return fmt.Sprintf(` +resource "google_monitoring_service" "srv" { + service_id = "tf-test-srv-%s" + display_name = "My Basic CloudEnpoints Service" + basic_service { + service_type = "CLOUD_ENDPOINTS" + service_labels = { + service = "%s" + } + } +} +`, randSuffix, endpoint) +} diff --git a/google/resource_monitoring_slo_test.go b/google/resource_monitoring_slo_test.go index dcbcaaa7f93..85bbbca3523 100644 --- a/google/resource_monitoring_slo_test.go +++ b/google/resource_monitoring_slo_test.go @@ -410,6 +410,30 @@ func TestAccMonitoringSlo_windowBasedMetricSumRangeSlis(t *testing.T) { }) } +func TestAccMonitoringSlo_genericService(t *testing.T) { + t.Parallel() + + randomSuffix := randString(t, 10) + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckMonitoringSloDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccMonitoringSlo_generic(randomSuffix), + }, + { + ResourceName: "google_monitoring_slo.primary", + ImportState: true, + ImportStateVerify: true, + // Ignore input-only field for import + ImportStateVerifyIgnore: []string{"service"}, + }, + }, + }) +} + func testAccMonitoringSlo_basic() string { return ` data "google_monitoring_app_engine_service" "ae" { @@ -457,6 +481,34 @@ resource "google_monitoring_slo" "primary" { ` } +func testAccMonitoringSlo_generic(randSuffix string) string { + return fmt.Sprintf(` +resource "google_monitoring_service" "srv" { + service_id = "tf-test-srv-%s" + display_name = "My Basic CloudEnpoints Service" + basic_service { + service_type = "CLOUD_ENDPOINTS" + service_labels = { + service = "another-endpoint" + } + } +} + + +resource "google_monitoring_slo" "primary" { + service = google_monitoring_service.srv.service_id + + goal = 0.9 + rolling_period_days = 1 + + basic_sli { + availability { + } + } +} +`, randSuffix) +} + func testAccMonitoringSlo_availabilitySli() string { return ` data "google_monitoring_app_engine_service" "ae" { diff --git a/website/docs/r/monitoring_service.html.markdown b/website/docs/r/monitoring_service.html.markdown new file mode 100644 index 00000000000..55de574d64d --- /dev/null +++ b/website/docs/r/monitoring_service.html.markdown @@ -0,0 +1,157 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** Type: MMv1 *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by Magic Modules and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in +# .github/CONTRIBUTING.md. +# +# ---------------------------------------------------------------------------- +subcategory: "Cloud (Stackdriver) Monitoring" +page_title: "Google: google_monitoring_service" +description: |- + A Service is a discrete, autonomous, and network-accessible unit, + designed to solve an individual concern (Wikipedia). +--- + +# google\_monitoring\_service + +A Service is a discrete, autonomous, and network-accessible unit, +designed to solve an individual concern (Wikipedia). In Cloud Monitoring, +a Service acts as the root resource under which operational aspects of +the service are accessible + + +To get more information about GenericService, see: + +* [API documentation](https://cloud.google.com/monitoring/api/ref_v3/rest/v3/services) +* How-to Guides + * [Service Monitoring](https://cloud.google.com/monitoring/service-monitoring) + * [Monitoring API Documentation](https://cloud.google.com/monitoring/api/v3/) + + +## Example Usage - Monitoring Service Example + + +```hcl +resource "google_monitoring_service" "my_service" { + service_id = "my-service" + display_name = "My Service my-service" + + user_labels = { + my_key = "my_value" + my_other_key = "my_other_value" + } + + basic_service { + service_type = "APP_ENGINE" + service_labels = { + module_id = "another-module-id" + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `service_id` - + (Required) + An optional service ID to use. If not given, the server will generate a + service ID. + + +- - - + + +* `display_name` - + (Optional) + Name used for UI elements listing this Service. + +* `user_labels` - + (Optional) + Labels which have been used to annotate the service. Label keys must start + with a letter. Label keys and values may contain lowercase letters, + numbers, underscores, and dashes. Label keys and values have a maximum + length of 63 characters, and must be less than 128 bytes in size. Up to 64 + label entries may be stored. For labels which do not have a semantic value, + the empty string may be supplied for the label value. + +* `basic_service` - + (Optional) + A well-known service type, defined by its service type and service labels. + Structure is [documented below](#nested_basic_service). + +* `project` - (Optional) The ID of the project in which the resource belongs. + If it is not provided, the provider project is used. + + +The `basic_service` block supports: + +* `service_type` - + (Optional) + The type of service that this basic service defines, e.g. + APP_ENGINE service type + +* `service_labels` - + (Optional) + Labels that specify the resource that emits the monitoring data + which is used for SLO reporting of this `Service`. + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `id` - an identifier for the resource with format `projects/{{project}}/services/{{service_id}}` + +* `name` - + The full resource name for this service. The syntax is: + projects/[PROJECT_ID]/services/[SERVICE_ID]. + +* `telemetry` - + Configuration for how to query telemetry on a Service. + Structure is [documented below](#nested_telemetry). + + +The `telemetry` block contains: + +* `resource_name` - + (Optional) + The full name of the resource that defines this service. + Formatted as described in + https://cloud.google.com/apis/design/resource_names. + +## Timeouts + +This resource provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - Default is 20 minutes. +- `update` - Default is 20 minutes. +- `delete` - Default is 20 minutes. + +## Import + + +GenericService can be imported using any of these accepted formats: + +``` +$ terraform import google_monitoring_service.default projects/{{project}}/services/{{service_id}} +$ terraform import google_monitoring_service.default {{project}}/{{service_id}} +$ terraform import google_monitoring_service.default {{service_id}} +``` + +## User Project Overrides + +This resource supports [User Project Overrides](https://www.terraform.io/docs/providers/google/guides/provider_reference.html#user_project_override).