From 00f475628d25db342dcd278cabc8da214c79bb95 Mon Sep 17 00:00:00 2001 From: Chris Stephens Date: Tue, 13 Nov 2018 22:24:00 +0000 Subject: [PATCH] Stackdriver notification channel --- google/provider_monitoring_gen.go | 5 +- ...esource_monitoring_notification_channel.go | 376 ++++++++++++++++++ ...ing_notification_channel_generated_test.go | 79 ++++ ...ce_monitoring_notification_channel_test.go | 49 +++ .../docs/r/container_cluster.html.markdown | 5 + ...itoring_notification_channel.html.markdown | 112 ++++++ 6 files changed, 624 insertions(+), 2 deletions(-) create mode 100644 google/resource_monitoring_notification_channel.go create mode 100644 google/resource_monitoring_notification_channel_generated_test.go create mode 100644 google/resource_monitoring_notification_channel_test.go create mode 100644 website/docs/r/monitoring_notification_channel.html.markdown diff --git a/google/provider_monitoring_gen.go b/google/provider_monitoring_gen.go index feba9e1f87c..959f0b4c511 100644 --- a/google/provider_monitoring_gen.go +++ b/google/provider_monitoring_gen.go @@ -17,6 +17,7 @@ package google import "github.com/hashicorp/terraform/helper/schema" var GeneratedMonitoringResourcesMap = map[string]*schema.Resource{ - "google_monitoring_alert_policy": resourceMonitoringAlertPolicy(), - "google_monitoring_group": resourceMonitoringGroup(), + "google_monitoring_alert_policy": resourceMonitoringAlertPolicy(), + "google_monitoring_group": resourceMonitoringGroup(), + "google_monitoring_notification_channel": resourceMonitoringNotificationChannel(), } diff --git a/google/resource_monitoring_notification_channel.go b/google/resource_monitoring_notification_channel.go new file mode 100644 index 00000000000..e8e419d17d7 --- /dev/null +++ b/google/resource_monitoring_notification_channel.go @@ -0,0 +1,376 @@ +// ---------------------------------------------------------------------------- +// +// *** 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" + + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceMonitoringNotificationChannel() *schema.Resource { + return &schema.Resource{ + Create: resourceMonitoringNotificationChannelCreate, + Read: resourceMonitoringNotificationChannelRead, + Update: resourceMonitoringNotificationChannelUpdate, + Delete: resourceMonitoringNotificationChannelDelete, + + Importer: &schema.ResourceImporter{ + State: resourceMonitoringNotificationChannelImport, + }, + + Schema: map[string]*schema.Schema{ + "display_name": { + Type: schema.TypeString, + Required: true, + }, + "type": { + Type: schema.TypeString, + Required: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "labels": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "user_labels": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "verification_status": { + Type: schema.TypeString, + Computed: true, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + } +} + +func resourceMonitoringNotificationChannelCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := make(map[string]interface{}) + labelsProp, err := expandMonitoringNotificationChannelLabels(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 + } + typeProp, err := expandMonitoringNotificationChannelType(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 + } + userLabelsProp, err := expandMonitoringNotificationChannelUserLabels(d.Get("user_labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("user_labels"); !isEmptyValue(reflect.ValueOf(userLabelsProp)) && (ok || !reflect.DeepEqual(v, userLabelsProp)) { + obj["userLabels"] = userLabelsProp + } + descriptionProp, err := expandMonitoringNotificationChannelDescription(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 := expandMonitoringNotificationChannelDisplayName(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 + } + enabledProp, err := expandMonitoringNotificationChannelEnabled(d.Get("enabled"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("enabled"); !isEmptyValue(reflect.ValueOf(enabledProp)) && (ok || !reflect.DeepEqual(v, enabledProp)) { + obj["enabled"] = enabledProp + } + + lockName, err := replaceVars(d, config, "stackdriver/notifications/{{project}}") + if err != nil { + return err + } + mutexKV.Lock(lockName) + defer mutexKV.Unlock(lockName) + + url, err := replaceVars(d, config, "https://monitoring.googleapis.com/v3/projects/{{project}}/notificationChannels") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new NotificationChannel: %#v", obj) + res, err := sendRequest(config, "POST", url, obj) + if err != nil { + return fmt.Errorf("Error creating NotificationChannel: %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) + + log.Printf("[DEBUG] Finished creating NotificationChannel %q: %#v", d.Id(), res) + + // `name` is autogenerated from the api so needs to be set post-create + name, ok := res["name"] + if !ok { + return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + } + d.Set("name", name.(string)) + d.SetId(name.(string)) + + return resourceMonitoringNotificationChannelRead(d, meta) +} + +func resourceMonitoringNotificationChannelRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + url, err := replaceVars(d, config, "https://monitoring.googleapis.com/v3/{{name}}") + if err != nil { + return err + } + + res, err := sendRequest(config, "GET", url, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("MonitoringNotificationChannel %q", d.Id())) + } + + if err := d.Set("labels", flattenMonitoringNotificationChannelLabels(res["labels"])); err != nil { + return fmt.Errorf("Error reading NotificationChannel: %s", err) + } + if err := d.Set("name", flattenMonitoringNotificationChannelName(res["name"])); err != nil { + return fmt.Errorf("Error reading NotificationChannel: %s", err) + } + if err := d.Set("verification_status", flattenMonitoringNotificationChannelVerificationStatus(res["verificationStatus"])); err != nil { + return fmt.Errorf("Error reading NotificationChannel: %s", err) + } + if err := d.Set("type", flattenMonitoringNotificationChannelType(res["type"])); err != nil { + return fmt.Errorf("Error reading NotificationChannel: %s", err) + } + if err := d.Set("user_labels", flattenMonitoringNotificationChannelUserLabels(res["userLabels"])); err != nil { + return fmt.Errorf("Error reading NotificationChannel: %s", err) + } + if err := d.Set("description", flattenMonitoringNotificationChannelDescription(res["description"])); err != nil { + return fmt.Errorf("Error reading NotificationChannel: %s", err) + } + if err := d.Set("display_name", flattenMonitoringNotificationChannelDisplayName(res["displayName"])); err != nil { + return fmt.Errorf("Error reading NotificationChannel: %s", err) + } + if err := d.Set("enabled", flattenMonitoringNotificationChannelEnabled(res["enabled"])); err != nil { + return fmt.Errorf("Error reading NotificationChannel: %s", err) + } + project, err := getProject(d, config) + if err != nil { + return err + } + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error reading NotificationChannel: %s", err) + } + + return nil +} + +func resourceMonitoringNotificationChannelUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := make(map[string]interface{}) + labelsProp, err := expandMonitoringNotificationChannelLabels(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 + } + typeProp, err := expandMonitoringNotificationChannelType(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 + } + userLabelsProp, err := expandMonitoringNotificationChannelUserLabels(d.Get("user_labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("user_labels"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, userLabelsProp)) { + obj["userLabels"] = userLabelsProp + } + descriptionProp, err := expandMonitoringNotificationChannelDescription(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 := expandMonitoringNotificationChannelDisplayName(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 + } + enabledProp, err := expandMonitoringNotificationChannelEnabled(d.Get("enabled"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("enabled"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, enabledProp)) { + obj["enabled"] = enabledProp + } + + lockName, err := replaceVars(d, config, "stackdriver/notifications/{{project}}") + if err != nil { + return err + } + mutexKV.Lock(lockName) + defer mutexKV.Unlock(lockName) + + url, err := replaceVars(d, config, "https://monitoring.googleapis.com/v3/{{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating NotificationChannel %q: %#v", d.Id(), obj) + _, err = sendRequest(config, "PATCH", url, obj) + + if err != nil { + return fmt.Errorf("Error updating NotificationChannel %q: %s", d.Id(), err) + } + + return resourceMonitoringNotificationChannelRead(d, meta) +} + +func resourceMonitoringNotificationChannelDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + lockName, err := replaceVars(d, config, "stackdriver/notifications/{{project}}") + if err != nil { + return err + } + mutexKV.Lock(lockName) + defer mutexKV.Unlock(lockName) + + url, err := replaceVars(d, config, "https://monitoring.googleapis.com/v3/{{name}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting NotificationChannel %q", d.Id()) + res, err := sendRequest(config, "DELETE", url, obj) + if err != nil { + return handleNotFoundError(err, d, "NotificationChannel") + } + + log.Printf("[DEBUG] Finished deleting NotificationChannel %q: %#v", d.Id(), res) + return nil +} + +func resourceMonitoringNotificationChannelImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + + config := meta.(*Config) + + // current import_formats can't import id's with forward slashes in them. + parseImportId([]string{"(?P.+)"}, d, config) + + return []*schema.ResourceData{d}, nil +} + +func flattenMonitoringNotificationChannelLabels(v interface{}) interface{} { + return v +} + +func flattenMonitoringNotificationChannelName(v interface{}) interface{} { + return v +} + +func flattenMonitoringNotificationChannelVerificationStatus(v interface{}) interface{} { + return v +} + +func flattenMonitoringNotificationChannelType(v interface{}) interface{} { + return v +} + +func flattenMonitoringNotificationChannelUserLabels(v interface{}) interface{} { + return v +} + +func flattenMonitoringNotificationChannelDescription(v interface{}) interface{} { + return v +} + +func flattenMonitoringNotificationChannelDisplayName(v interface{}) interface{} { + return v +} + +func flattenMonitoringNotificationChannelEnabled(v interface{}) interface{} { + return v +} + +func expandMonitoringNotificationChannelLabels(v interface{}, d *schema.ResourceData, 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 expandMonitoringNotificationChannelType(v interface{}, d *schema.ResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandMonitoringNotificationChannelUserLabels(v interface{}, d *schema.ResourceData, 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 expandMonitoringNotificationChannelDescription(v interface{}, d *schema.ResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandMonitoringNotificationChannelDisplayName(v interface{}, d *schema.ResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandMonitoringNotificationChannelEnabled(v interface{}, d *schema.ResourceData, config *Config) (interface{}, error) { + return v, nil +} diff --git a/google/resource_monitoring_notification_channel_generated_test.go b/google/resource_monitoring_notification_channel_generated_test.go new file mode 100644 index 00000000000..f1907493e18 --- /dev/null +++ b/google/resource_monitoring_notification_channel_generated_test.go @@ -0,0 +1,79 @@ +// ---------------------------------------------------------------------------- +// +// *** 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" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccMonitoringNotificationChannel_notificationChannelBasicExample(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckMonitoringNotificationChannelDestroy, + Steps: []resource.TestStep{ + { + Config: testAccMonitoringNotificationChannel_notificationChannelBasicExample(acctest.RandString(10)), + }, + { + ResourceName: "google_monitoring_notification_channel.basic", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccMonitoringNotificationChannel_notificationChannelBasicExample(val string) string { + return fmt.Sprintf(` +resource "google_monitoring_notification_channel" "basic" { + display_name = "Test Notification Channel-%s" + type = "email" + labels = { + email_address = "fake_email@blahblah.com" + } +} +`, val, + ) +} + +func testAccCheckMonitoringNotificationChannelDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_monitoring_notification_channel" { + continue + } + + config := testAccProvider.Meta().(*Config) + + url, err := replaceVarsForTest(rs, "https://monitoring.googleapis.com/v3/{{name}}") + if err != nil { + return err + } + + _, err = sendRequest(config, "GET", url, nil) + if err == nil { + return fmt.Errorf("MonitoringNotificationChannel still exists at %s", url) + } + } + + return nil +} diff --git a/google/resource_monitoring_notification_channel_test.go b/google/resource_monitoring_notification_channel_test.go new file mode 100644 index 00000000000..3d26c8cb7af --- /dev/null +++ b/google/resource_monitoring_notification_channel_test.go @@ -0,0 +1,49 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccMonitoringNotificationChannel_update(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckMonitoringNotificationChannelDestroy, + Steps: []resource.TestStep{ + { + Config: testAccMonitoringNotificationChannel_update("email", `email_address = "fake_email@blahblah.com"`), + }, + { + ResourceName: "google_monitoring_notification_channel.update", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccMonitoringNotificationChannel_update("sms", `number = "+15555379009"`), + }, + { + ResourceName: "google_monitoring_notification_channel.update", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccMonitoringNotificationChannel_update(channel, labels string) string { + return fmt.Sprintf(` +resource "google_monitoring_notification_channel" "update" { + display_name = "IntTest Notification Channel" + type = "%s" + labels = { + %s + } +} +`, channel, labels, + ) +} diff --git a/website/docs/r/container_cluster.html.markdown b/website/docs/r/container_cluster.html.markdown index cf47a9dbc92..2f3644f606c 100644 --- a/website/docs/r/container_cluster.html.markdown +++ b/website/docs/r/container_cluster.html.markdown @@ -357,6 +357,11 @@ The `node_config` block supports: * `service_account` - (Optional) The service account to be used by the Node VMs. If not specified, the "default" service account is used. + In order to use the configured `oauth_scopes` for logging and monitoring, the service account being used needs the + [roles/logging.logWriter](https://cloud.google.com/iam/docs/understanding-roles#stackdriver_logging_roles) and + [roles/monitoring.metricWriter](https://cloud.google.com/iam/docs/understanding-roles#stackdriver_monitoring_roles) roles. + + -> Projects that enable the [Cloud Compute Engine API](https://cloud.google.com/compute/) with Terraform may need these roles added manually to the service account. Projects that enable the API in the Cloud Console should have them added automatically. * `tags` - (Optional) The list of instance tags applied to all nodes. Tags are used to identify valid sources or targets for network firewalls. diff --git a/website/docs/r/monitoring_notification_channel.html.markdown b/website/docs/r/monitoring_notification_channel.html.markdown new file mode 100644 index 00000000000..b6303d7f7e3 --- /dev/null +++ b/website/docs/r/monitoring_notification_channel.html.markdown @@ -0,0 +1,112 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** 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. +# +# ---------------------------------------------------------------------------- +layout: "google" +page_title: "Google: google_monitoring_notification_channel" +sidebar_current: "docs-google-monitoring-notification-channel" +description: |- + A NotificationChannel is a medium through which an alert is delivered + when a policy violation is detected. +--- + +# google\_monitoring\_notification\_channel + +A NotificationChannel is a medium through which an alert is delivered +when a policy violation is detected. Examples of channels include email, SMS, +and third-party messaging applications. Fields containing sensitive information +like authentication tokens or contact info are only partially populated on retrieval. + + +To get more information about NotificationChannel, see: + +* [API documentation](https://cloud.google.com/monitoring/api/ref_v3/rest/v3/projects.notificationChannels) +* How-to Guides + * [Official Documentation](https://cloud.google.com/monitoring/api/v3/) + + +## Example Usage - Notification Channel Basic + + +```hcl +resource "google_monitoring_notification_channel" "basic" { + display_name = "Test Notification Channel" + type = "email" + labels = { + email_address = "fake_email@blahblah.com" + } +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `type` - + (Required) + The type of the notification channel. This field matches the value of the NotificationChannelDescriptor.type field. See https://cloud.google.com/monitoring/api/ref_v3/rest/v3/projects.notificationChannelDescriptors/list to get the list of valid values such as "email", "slack", etc... + +* `display_name` - + (Required) + An optional human-readable name for this notification channel. It is recommended that you specify a non-empty and unique name in order to make it easier to identify the channels in your project, though this is not enforced. The display name is limited to 512 Unicode characters. + + +- - - + + +* `labels` - + (Optional) + Configuration fields that define the channel and its behavior. The permissible and required labels are specified in the NotificationChannelDescriptor.labels of the NotificationChannelDescriptor corresponding to the type field. + +* `user_labels` - + (Optional) + User-supplied key/value data that does not need to conform to the corresponding NotificationChannelDescriptor's schema, unlike the labels field. This field is intended to be used for organizing and identifying the NotificationChannel objects.The field can contain up to 64 entries. Each key and value is limited to 63 Unicode characters or 128 bytes, whichever is smaller. Labels and values can contain only lowercase letters, numerals, underscores, and dashes. Keys must begin with a letter. + +* `description` - + (Optional) + An optional human-readable description of this notification channel. This description may provide additional details, beyond the display name, for the channel. This may not exceeed 1024 Unicode characters. + +* `enabled` - + (Optional) + Whether notifications are forwarded to the described channel. This makes it possible to disable delivery of notifications to a particular channel without removing the channel from all alerting policies that reference the channel. This is a more convenient approach when the change is temporary and you want to receive notifications from the same set of alerting policies on the channel at some point in the future. +* `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: + + +* `name` - + The full REST resource name for this channel. The syntax is: + projects/[PROJECT_ID]/notificationChannels/[CHANNEL_ID] + The [CHANNEL_ID] is automatically assigned by the server on creation. + +* `verification_status` - + Indicates whether this channel has been verified or not. On a ListNotificationChannels or GetNotificationChannel operation, this field is expected to be populated.If the value is UNVERIFIED, then it indicates that the channel is non-functioning (it both requires verification and lacks verification); otherwise, it is assumed that the channel works.If the channel is neither VERIFIED nor UNVERIFIED, it implies that the channel is of a type that does not require verification or that this specific channel has been exempted from verification because it was created prior to verification being required for channels of this type.This field cannot be modified using a standard UpdateNotificationChannel operation. To change the value of this field, you must call VerifyNotificationChannel. + + + +## Import + +NotificationChannel can be imported using any of these accepted formats: + +``` +$ terraform import google_monitoring_notification_channel.default {{name}} +```