From db0d66c12ed4a12852ffbbd5e163b52ba78b9805 Mon Sep 17 00:00:00 2001 From: Joe Selman Date: Tue, 26 Sep 2017 17:41:41 -0700 Subject: [PATCH 1/4] Fix bad page title --- website/docs/r/logging_billing_account_sink.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/logging_billing_account_sink.html.markdown b/website/docs/r/logging_billing_account_sink.html.markdown index 5063de7f20f..342dee1e9f2 100644 --- a/website/docs/r/logging_billing_account_sink.html.markdown +++ b/website/docs/r/logging_billing_account_sink.html.markdown @@ -1,6 +1,6 @@ --- layout: "google" -page_title: "Google: google_logging_billing-account_sink" +page_title: "Google: google_logging_billing_account_sink" sidebar_current: "docs-google-logging-billing-account-sink" description: |- Manages a billing account logging sink. From 560259b86495439565412e810e726f5972b2cccd Mon Sep 17 00:00:00 2001 From: Joe Selman Date: Tue, 26 Sep 2017 17:41:53 -0700 Subject: [PATCH 2/4] Add resource logging_folder_sink --- google/provider.go | 1 + google/resource_logging_folder_sink.go | 95 ++++++++ google/resource_logging_folder_sink_test.go | 218 ++++++++++++++++++ google/utils.go | 6 + .../docs/r/logging_folder_sink.html.markdown | 81 +++++++ website/google.erb | 4 + 6 files changed, 405 insertions(+) create mode 100644 google/resource_logging_folder_sink.go create mode 100644 google/resource_logging_folder_sink_test.go create mode 100644 website/docs/r/logging_folder_sink.html.markdown diff --git a/google/provider.go b/google/provider.go index 0a202fe9ca3..596ff0c37c8 100644 --- a/google/provider.go +++ b/google/provider.go @@ -109,6 +109,7 @@ func Provider() terraform.ResourceProvider { "google_folder": resourceGoogleFolder(), "google_folder_iam_policy": resourceGoogleFolderIamPolicy(), "google_logging_billing_account_sink": resourceLoggingBillingAccountSink(), + "google_logging_folder_sink": resourceLoggingFolderSink(), "google_logging_project_sink": resourceLoggingProjectSink(), "google_sourcerepo_repository": resourceSourceRepoRepository(), "google_spanner_instance": resourceSpannerInstance(), diff --git a/google/resource_logging_folder_sink.go b/google/resource_logging_folder_sink.go new file mode 100644 index 00000000000..4fb29161450 --- /dev/null +++ b/google/resource_logging_folder_sink.go @@ -0,0 +1,95 @@ +package google + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/schema" + "strings" +) + +func resourceLoggingFolderSink() *schema.Resource { + schm := &schema.Resource{ + Create: resourceLoggingFolderSinkCreate, + Read: resourceLoggingFolderSinkRead, + Delete: resourceLoggingFolderSinkDelete, + Update: resourceLoggingFolderSinkUpdate, + Schema: resourceLoggingSinkSchema(), + } + schm.Schema["folder"] = &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: optionalPrefixSuppress("folders/"), + } + schm.Schema["include_children"] = &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + Default: false, + } + + return schm +} + +func resourceLoggingFolderSinkCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + folder := d.Get("folder").(string) + if strings.HasPrefix(folder, "folders/") { + folder = folder[len("folders/"):] + } + + id, sink := expandResourceLoggingSink(d, "folders", folder) + sink.IncludeChildren = d.Get("include_children").(bool) + + // The API will reject any requests that don't explicitly set 'uniqueWriterIdentity' to true. + _, err := config.clientLogging.Folders.Sinks.Create(id.parent(), sink).UniqueWriterIdentity(true).Do() + if err != nil { + return err + } + + d.SetId(id.canonicalId()) + return resourceLoggingFolderSinkRead(d, meta) +} + +func resourceLoggingFolderSinkRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + sink, err := config.clientLogging.Folders.Sinks.Get(d.Id()).Do() + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("Folder Logging Sink %s", d.Get("name").(string))) + } + + flattenResourceLoggingSink(d, sink) + d.Set("include_children", sink.IncludeChildren) + + return nil +} + +func resourceLoggingFolderSinkUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + sink := expandResourceLoggingSinkForUpdate(d) + // It seems the API might actually accept an update for include_children; this is not in the list of updatable + // properties though and might break in the future. Always include the value to prevent it changing. + sink.IncludeChildren = d.Get("include_children").(bool) + sink.ForceSendFields = append(sink.ForceSendFields, "IncludeChildren") + + // The API will reject any requests that don't explicitly set 'uniqueWriterIdentity' to true. + _, err := config.clientLogging.Folders.Sinks.Patch(d.Id(), sink).UniqueWriterIdentity(true).Do() + if err != nil { + return err + } + + return resourceLoggingFolderSinkRead(d, meta) +} + +func resourceLoggingFolderSinkDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + _, err := config.clientLogging.Projects.Sinks.Delete(d.Id()).Do() + if err != nil { + return err + } + + return nil +} diff --git a/google/resource_logging_folder_sink_test.go b/google/resource_logging_folder_sink_test.go new file mode 100644 index 00000000000..474a3c7f8ec --- /dev/null +++ b/google/resource_logging_folder_sink_test.go @@ -0,0 +1,218 @@ +package google + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/logging/v2" + "strconv" +) + +func TestAccLoggingFolderSink_basic(t *testing.T) { + skipIfEnvNotSet(t, "GOOGLE_ORG") + + sinkName := "tf-test-sink-" + acctest.RandString(10) + bucketName := "tf-test-sink-bucket-" + acctest.RandString(10) + folderName := "tf-test-folder-" + acctest.RandString(10) + org := os.Getenv("GOOGLE_ORG") + + var sink logging.LogSink + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLoggingFolderSinkDestroy, + Steps: []resource.TestStep{ + { + Config: testAccLoggingFolderSink_basic(sinkName, bucketName, folderName, "organizations/"+org), + Check: resource.ComposeTestCheckFunc( + testAccCheckLoggingFolderSinkExists("google_logging_folder_sink.basic", &sink), + testAccCheckLoggingFolderSink(&sink, "google_logging_folder_sink.basic"), + ), + }, + }, + }) +} + +func TestAccLoggingFolderSink_folderAcceptsFullFolderPath(t *testing.T) { + skipIfEnvNotSet(t, "GOOGLE_ORG") + + sinkName := "tf-test-sink-" + acctest.RandString(10) + bucketName := "tf-test-sink-bucket-" + acctest.RandString(10) + folderName := "tf-test-folder-" + acctest.RandString(10) + org := os.Getenv("GOOGLE_ORG") + + var sink logging.LogSink + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLoggingFolderSinkDestroy, + Steps: []resource.TestStep{ + { + Config: testAccLoggingFolderSink_withFullFolderPath(sinkName, bucketName, folderName, "organizations/"+org), + Check: resource.ComposeTestCheckFunc( + testAccCheckLoggingFolderSinkExists("google_logging_folder_sink.basic", &sink), + testAccCheckLoggingFolderSink(&sink, "google_logging_folder_sink.basic"), + ), + }, + }, + }) +} + +func TestAccLoggingFolderSink_update(t *testing.T) { + skipIfEnvNotSet(t, "GOOGLE_ORG") + + sinkName := "tf-test-sink-" + acctest.RandString(10) + bucketName := "tf-test-sink-bucket-" + acctest.RandString(10) + updatedBucketName := "tf-test-sink-bucket-" + acctest.RandString(10) + folderName := "tf-test-folder-" + acctest.RandString(10) + parent := "organizations/" + os.Getenv("GOOGLE_ORG") + + var sinkBefore, sinkAfter logging.LogSink + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLoggingFolderSinkDestroy, + Steps: []resource.TestStep{ + { + Config: testAccLoggingFolderSink_basic(sinkName, bucketName, folderName, parent), + Check: resource.ComposeTestCheckFunc( + testAccCheckLoggingFolderSinkExists("google_logging_folder_sink.basic", &sinkBefore), + testAccCheckLoggingFolderSink(&sinkBefore, "google_logging_folder_sink.basic"), + ), + }, { + Config: testAccLoggingFolderSink_basic(sinkName, updatedBucketName, folderName, parent), + Check: resource.ComposeTestCheckFunc( + testAccCheckLoggingFolderSinkExists("google_logging_folder_sink.basic", &sinkAfter), + testAccCheckLoggingFolderSink(&sinkAfter, "google_logging_folder_sink.basic"), + ), + }, + }, + }) + + // Destination should have changed, but WriterIdentity should be the same + if sinkBefore.Destination == sinkAfter.Destination { + t.Errorf("Expected Destination to change, but it didn't: Destination = %#v", sinkBefore.Destination) + } + if sinkBefore.WriterIdentity != sinkAfter.WriterIdentity { + t.Errorf("Expected WriterIdentity to be the same, but it differs: before = %#v, after = %#v", + sinkBefore.WriterIdentity, sinkAfter.WriterIdentity) + } +} + +func testAccCheckLoggingFolderSinkDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_logging_folder_sink" { + continue + } + + attributes := rs.Primary.Attributes + + _, err := config.clientLogging.Folders.Sinks.Get(attributes["id"]).Do() + if err == nil { + return fmt.Errorf("folder sink still exists") + } + } + + return nil +} + +func testAccCheckLoggingFolderSinkExists(n string, sink *logging.LogSink) resource.TestCheckFunc { + return func(s *terraform.State) error { + attributes, err := getResourceAttributes(n, s) + if err != nil { + return err + } + config := testAccProvider.Meta().(*Config) + + si, err := config.clientLogging.Folders.Sinks.Get(attributes["id"]).Do() + if err != nil { + return err + } + *sink = *si + + return nil + } +} + +func testAccCheckLoggingFolderSink(sink *logging.LogSink, n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + attributes, err := getResourceAttributes(n, s) + if err != nil { + return err + } + + if sink.Destination != attributes["destination"] { + return fmt.Errorf("mismatch on destination: api has %s but client has %s", sink.Destination, attributes["destination"]) + } + + if sink.Filter != attributes["filter"] { + return fmt.Errorf("mismatch on filter: api has %s but client has %s", sink.Filter, attributes["filter"]) + } + + if sink.WriterIdentity != attributes["writer_identity"] { + return fmt.Errorf("mismatch on writer_identity: api has %s but client has %s", sink.WriterIdentity, attributes["writer_identity"]) + } + + includeChildren := false + if attributes["include_children"] != "" { + includeChildren, err = strconv.ParseBool(attributes["include_children"]) + if err != nil { + return err + } + } + if sink.IncludeChildren != includeChildren { + return fmt.Errorf("mismatch on include_children: api has %s but client has %s", sink.IncludeChildren, attributes["include_children"]) + } + + return nil + } +} + +func testAccLoggingFolderSink_basic(sinkName, bucketName, folderName, folderParent string) string { + return fmt.Sprintf(` +resource "google_logging_folder_sink" "basic" { + name = "%s" + folder = "${element(split("/", google_folder.my-folder.name), 1)}" + destination = "storage.googleapis.com/${google_storage_bucket.log-bucket.name}" + filter = "logName=\"projects/%s/logs/compute.googleapis.com%%2Factivity_log\" AND severity>=ERROR" + include_children = true +} + +resource "google_storage_bucket" "log-bucket" { + name = "%s" +} + +resource "google_folder" "my-folder" { + display_name = "%s" + parent = "%s" +}`, sinkName, getTestProjectFromEnv(), bucketName, folderName, folderParent) +} + +func testAccLoggingFolderSink_withFullFolderPath(sinkName, bucketName, folderName, folderParent string) string { + return fmt.Sprintf(` +resource "google_logging_folder_sink" "basic" { + name = "%s" + folder = "${google_folder.my-folder.name}" + destination = "storage.googleapis.com/${google_storage_bucket.log-bucket.name}" + filter = "logName=\"projects/%s/logs/compute.googleapis.com%%2Factivity_log\" AND severity>=ERROR" + include_children = false +} + +resource "google_storage_bucket" "log-bucket" { + name = "%s" +} + +resource "google_folder" "my-folder" { + display_name = "%s" + parent = "%s" +}`, sinkName, getTestProjectFromEnv(), bucketName, folderName, folderParent) +} diff --git a/google/utils.go b/google/utils.go index 9cb81e6f2f4..f85cd1ff54e 100644 --- a/google/utils.go +++ b/google/utils.go @@ -258,6 +258,12 @@ func linkDiffSuppress(k, old, new string, d *schema.ResourceData) bool { return false } +func optionalPrefixSuppress(prefix string) schema.SchemaDiffSuppressFunc { + return func(k, old, new string, d *schema.ResourceData) bool { + return prefix+old == new || prefix+new == old + } +} + func ipCidrRangeDiffSuppress(k, old, new string, d *schema.ResourceData) bool { // The range may be a: // A) single IP address (e.g. 10.2.3.4) diff --git a/website/docs/r/logging_folder_sink.html.markdown b/website/docs/r/logging_folder_sink.html.markdown new file mode 100644 index 00000000000..0ae8bf6b550 --- /dev/null +++ b/website/docs/r/logging_folder_sink.html.markdown @@ -0,0 +1,81 @@ +--- +layout: "google" +page_title: "Google: google_logging_folder_sink" +sidebar_current: "docs-google-logging-folder-sink" +description: |- + Manages a folder-level logging sink. +--- + +# google\_logging\_folder\_sink + +Manages a folder-level logging sink. For more information see +[the official documentation](https://cloud.google.com/logging/docs/) and +[Exporting Logs in the API](https://cloud.google.com/logging/docs/api/tasks/exporting-logs). + +Note that you must have the "Logs Configuration Writer" IAM role (`roles/logging.configWriter`) +granted to the credentials used with terraform. + +## Example Usage + +```hcl +resource "google_logging_folder_sink" "my-sink" { + name = "my-sink" + folder = "${google_folder.my-folder.name}" + + # Can export to pubsub, cloud storage, or bigtable + destination = "storage.googleapis.com/${google_storage_bucket.log-bucket.name}" + + # Log all WARN or higher severity messages relating to instances + filter = "resource.type = gce_instance AND severity >= WARN" +} + +resource "google_storage_bucket" "log-bucket" { + name = "folder-logging-bucket" +} + +resource "google_project_iam_binding" "log-writer" { + role = "roles/storage.objectCreator" + + members = [ + "${google_logging_folder_sink.my-sink.writer_identity}", + ] +} + +resource "google_folder" "my-folder" { + display_name = "My folder" + parent = "organizations/123456" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the logging sink. + +* `folder` - (Required) The folder to be exported to the sink. Note that either [FOLDER_ID] or "folders/[FOLDER_ID]" is + accepted. + +* `destination` - (Required) The destination of the sink (or, in other words, where logs are written to). Can be a + Cloud Storage bucket, a PubSub topic, or a BigQuery dataset. Examples: +``` +"storage.googleapis.com/[GCS_BUCKET]" +"bigquery.googleapis.com/projects/[PROJECT_ID]/datasets/[DATASET]" +"pubsub.googleapis.com/projects/[PROJECT_ID]/topics/[TOPIC_ID]" +``` + The writer associated with the sink must have access to write to the above resource. + +* `filter` - (Optional) The filter to apply when exporting logs. Only log entries that match the filter are exported. + See (Advanced Log Filters)[https://cloud.google.com/logging/docs/view/advanced_filters] for information on how to + write a filter. + +* `include_children` - (Optional) Whether or not to include children folders in the sink export. If true, logs + associated with child projects are also exported; otherwise only logs relating to the provided folder are included. + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are +exported: + +* `writer_identity` - The identity associated with this sink. This identity must be granted write access to the + configured `destination`. diff --git a/website/google.erb b/website/google.erb index 7350c554763..df07d74a5a3 100644 --- a/website/google.erb +++ b/website/google.erb @@ -346,6 +346,10 @@ google_logging_billing_account_sink + > + google_logging_folder_sink + + > google_logging_project_sink From 21089f2896aabcede577d418b3ad2b992574af2e Mon Sep 17 00:00:00 2001 From: Joe Selman Date: Wed, 27 Sep 2017 11:07:05 -0700 Subject: [PATCH 3/4] Use proper parse function and string format --- google/resource_logging_folder_sink.go | 6 +----- google/resource_logging_folder_sink_test.go | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/google/resource_logging_folder_sink.go b/google/resource_logging_folder_sink.go index 4fb29161450..797f0e660ce 100644 --- a/google/resource_logging_folder_sink.go +++ b/google/resource_logging_folder_sink.go @@ -33,11 +33,7 @@ func resourceLoggingFolderSink() *schema.Resource { func resourceLoggingFolderSinkCreate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - folder := d.Get("folder").(string) - if strings.HasPrefix(folder, "folders/") { - folder = folder[len("folders/"):] - } - + folder := parseFolderId(d.Get("folder")) id, sink := expandResourceLoggingSink(d, "folders", folder) sink.IncludeChildren = d.Get("include_children").(bool) diff --git a/google/resource_logging_folder_sink_test.go b/google/resource_logging_folder_sink_test.go index 474a3c7f8ec..cfecc402f64 100644 --- a/google/resource_logging_folder_sink_test.go +++ b/google/resource_logging_folder_sink_test.go @@ -170,7 +170,7 @@ func testAccCheckLoggingFolderSink(sink *logging.LogSink, n string) resource.Tes } } if sink.IncludeChildren != includeChildren { - return fmt.Errorf("mismatch on include_children: api has %s but client has %s", sink.IncludeChildren, attributes["include_children"]) + return fmt.Errorf("mismatch on include_children: api has %v but client has %v", sink.IncludeChildren, includeChildren) } return nil From 2790cde8cb337c926dbcb389b4caac90b0131e2c Mon Sep 17 00:00:00 2001 From: Joe Selman Date: Wed, 27 Sep 2017 11:12:36 -0700 Subject: [PATCH 4/4] Remove unused strings --- google/resource_logging_folder_sink.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google/resource_logging_folder_sink.go b/google/resource_logging_folder_sink.go index 797f0e660ce..f4798842805 100644 --- a/google/resource_logging_folder_sink.go +++ b/google/resource_logging_folder_sink.go @@ -2,8 +2,8 @@ package google import ( "fmt" + "github.com/hashicorp/terraform/helper/schema" - "strings" ) func resourceLoggingFolderSink() *schema.Resource {