Add google_logging_folder_sink resource (#470)

* Fix bad page title

* Add resource logging_folder_sink

* Use proper parse function and string format

* Remove unused strings
This commit is contained in:
Joe Selman 2017-10-03 09:26:19 -07:00 committed by GitHub
parent 547f20a803
commit ef543b20c5
7 changed files with 402 additions and 1 deletions

View File

@ -108,6 +108,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(),

View File

@ -0,0 +1,91 @@
package google
import (
"fmt"
"github.com/hashicorp/terraform/helper/schema"
)
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 := parseFolderId(d.Get("folder"))
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
}

View File

@ -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 %v but client has %v", sink.IncludeChildren, includeChildren)
}
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)
}

View File

@ -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)

View File

@ -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.

View File

@ -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`.

View File

@ -342,6 +342,10 @@
<a href="/docs/providers/google/r/logging_billing_account_sink.html">google_logging_billing_account_sink</a>
</li>
<li<%= sidebar_current("docs-google-logging-folder-sink") %>>
<a href="/docs/providers/google/r/logging_folder_sink.html">google_logging_folder_sink</a>
</li>
<li<%= sidebar_current("docs-google-logging-project-sink") %>>
<a href="/docs/providers/google/r/logging_project_sink.html">google_logging_project_sink</a>
</li>