-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add stackdriver project sink support (#432)
* Vendor cloud logging api * Add logging sink support * Remove typo * Set Filter simpler * Rename typ, typName to resourceType, resourceId * Handle notFoundError * Use # instead of // for hcl comments * Cleanup test code * Change testAccCheckLoggingProjectSink to take a provided api object * Fix whitespace change after merge conflict
- Loading branch information
Showing
11 changed files
with
15,288 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package google | ||
|
||
import ( | ||
"fmt" | ||
"regexp" | ||
) | ||
|
||
// loggingSinkResourceTypes contains all the possible Stackdriver Logging resource types. Used to parse ids safely. | ||
var loggingSinkResourceTypes = []string{ | ||
"billingAccount", | ||
"folders", | ||
"organizations", | ||
"projects", | ||
} | ||
|
||
// LoggingSinkId represents the parts that make up the canonical id used within terraform for a logging resource. | ||
type LoggingSinkId struct { | ||
resourceType string | ||
resourceId string | ||
name string | ||
} | ||
|
||
// loggingSinkIdRegex matches valid logging sink canonical ids | ||
var loggingSinkIdRegex = regexp.MustCompile("(.+)/(.+)/sinks/(.+)") | ||
|
||
// canonicalId returns the LoggingSinkId as the canonical id used within terraform. | ||
func (l LoggingSinkId) canonicalId() string { | ||
return fmt.Sprintf("%s/%s/sinks/%s", l.resourceType, l.resourceId, l.name) | ||
} | ||
|
||
// parent returns the "parent-level" resource that the sink is in (e.g. `folders/foo` for id `folders/foo/sinks/bar`) | ||
func (l LoggingSinkId) parent() string { | ||
return fmt.Sprintf("%s/%s", l.resourceType, l.resourceId) | ||
} | ||
|
||
// parseLoggingSinkId parses a canonical id into a LoggingSinkId, or returns an error on failure. | ||
func parseLoggingSinkId(id string) (*LoggingSinkId, error) { | ||
parts := loggingSinkIdRegex.FindStringSubmatch(id) | ||
if parts == nil { | ||
return nil, fmt.Errorf("unable to parse logging sink id %#v", id) | ||
} | ||
// If our resourceType is not a valid logging sink resource type, complain loudly | ||
validLoggingSinkResourceType := false | ||
for _, v := range loggingSinkResourceTypes { | ||
if v == parts[1] { | ||
validLoggingSinkResourceType = true | ||
break | ||
} | ||
} | ||
|
||
if !validLoggingSinkResourceType { | ||
return nil, fmt.Errorf("Logging resource type %s is not valid. Valid resource types: %#v", parts[1], | ||
loggingSinkResourceTypes) | ||
} | ||
return &LoggingSinkId{ | ||
resourceType: parts[1], | ||
resourceId: parts[2], | ||
name: parts[3], | ||
}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package google | ||
|
||
import "testing" | ||
|
||
func TestParseLoggingSinkId(t *testing.T) { | ||
tests := []struct { | ||
val string | ||
out *LoggingSinkId | ||
errExpected bool | ||
}{ | ||
{"projects/my-project/sinks/my-sink", &LoggingSinkId{"projects", "my-project", "my-sink"}, false}, | ||
{"folders/foofolder/sinks/woo", &LoggingSinkId{"folders", "foofolder", "woo"}, false}, | ||
{"kitchens/the-big-one/sinks/second-from-the-left", nil, true}, | ||
} | ||
|
||
for _, test := range tests { | ||
out, err := parseLoggingSinkId(test.val) | ||
if err != nil { | ||
if !test.errExpected { | ||
t.Errorf("Got error with val %#v: error = %#v", test.val, err) | ||
} | ||
} else { | ||
if *out != *test.out { | ||
t.Errorf("Mismatch on val %#v: expected %#v but got %#v", test.val, test.out, out) | ||
} | ||
} | ||
} | ||
} | ||
|
||
func TestLoggingSinkId(t *testing.T) { | ||
tests := []struct { | ||
val LoggingSinkId | ||
canonicalId string | ||
parent string | ||
}{ | ||
{ | ||
val: LoggingSinkId{"projects", "my-project", "my-sink"}, | ||
canonicalId: "projects/my-project/sinks/my-sink", | ||
parent: "projects/my-project", | ||
}, { | ||
val: LoggingSinkId{"folders", "foofolder", "woo"}, | ||
canonicalId: "folders/foofolder/sinks/woo", | ||
parent: "folders/foofolder", | ||
}, | ||
} | ||
|
||
for _, test := range tests { | ||
canonicalId := test.val.canonicalId() | ||
|
||
if canonicalId != test.canonicalId { | ||
t.Errorf("canonicalId mismatch on val %#v: expected %#v but got %#v", test.val, test.canonicalId, canonicalId) | ||
} | ||
|
||
parent := test.val.parent() | ||
|
||
if parent != test.parent { | ||
t.Errorf("parent mismatch on val %#v: expected %#v but got %#v", test.val, test.parent, parent) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
package google | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/hashicorp/terraform/helper/schema" | ||
"google.golang.org/api/logging/v2" | ||
) | ||
|
||
const nonUniqueWriterAccount = "serviceAccount:[email protected]" | ||
|
||
func resourceLoggingProjectSink() *schema.Resource { | ||
return &schema.Resource{ | ||
Create: resourceLoggingProjectSinkCreate, | ||
Read: resourceLoggingProjectSinkRead, | ||
Delete: resourceLoggingProjectSinkDelete, | ||
Update: resourceLoggingProjectSinkUpdate, | ||
Schema: map[string]*schema.Schema{ | ||
"name": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
}, | ||
|
||
"destination": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
}, | ||
|
||
"filter": { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
}, | ||
|
||
"project": { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ForceNew: true, | ||
}, | ||
|
||
"unique_writer_identity": { | ||
Type: schema.TypeBool, | ||
Optional: true, | ||
Default: false, | ||
ForceNew: true, | ||
}, | ||
|
||
"writer_identity": { | ||
Type: schema.TypeString, | ||
Computed: true, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func resourceLoggingProjectSinkCreate(d *schema.ResourceData, meta interface{}) error { | ||
config := meta.(*Config) | ||
|
||
project, err := getProject(d, config) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
name := d.Get("name").(string) | ||
|
||
id := LoggingSinkId{ | ||
resourceType: "projects", | ||
resourceId: project, | ||
name: name, | ||
} | ||
|
||
sink := logging.LogSink{ | ||
Name: d.Get("name").(string), | ||
Destination: d.Get("destination").(string), | ||
Filter: d.Get("filter").(string), | ||
} | ||
|
||
uniqueWriterIdentity := d.Get("unique_writer_identity").(bool) | ||
|
||
_, err = config.clientLogging.Projects.Sinks.Create(id.parent(), &sink).UniqueWriterIdentity(uniqueWriterIdentity).Do() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
d.SetId(id.canonicalId()) | ||
|
||
return resourceLoggingProjectSinkRead(d, meta) | ||
} | ||
|
||
func resourceLoggingProjectSinkRead(d *schema.ResourceData, meta interface{}) error { | ||
config := meta.(*Config) | ||
|
||
sink, err := config.clientLogging.Projects.Sinks.Get(d.Id()).Do() | ||
if err != nil { | ||
return handleNotFoundError(err, d, fmt.Sprintf("Project Logging Sink %s", d.Get("name").(string))) | ||
} | ||
|
||
d.Set("name", sink.Name) | ||
d.Set("destination", sink.Destination) | ||
d.Set("filter", sink.Filter) | ||
d.Set("writer_identity", sink.WriterIdentity) | ||
if sink.WriterIdentity != nonUniqueWriterAccount { | ||
d.Set("unique_writer_identity", true) | ||
} else { | ||
d.Set("unique_writer_identity", false) | ||
} | ||
return nil | ||
} | ||
|
||
func resourceLoggingProjectSinkUpdate(d *schema.ResourceData, meta interface{}) error { | ||
config := meta.(*Config) | ||
|
||
// Can only update destination/filter right now. Despite the method below using 'Patch', the API requires both | ||
// destination and filter (even if unchanged). | ||
sink := logging.LogSink{ | ||
Destination: d.Get("destination").(string), | ||
Filter: d.Get("filter").(string), | ||
} | ||
|
||
if d.HasChange("destination") { | ||
sink.ForceSendFields = append(sink.ForceSendFields, "Destination") | ||
} | ||
if d.HasChange("filter") { | ||
sink.ForceSendFields = append(sink.ForceSendFields, "Filter") | ||
} | ||
|
||
uniqueWriterIdentity := d.Get("unique_writer_identity").(bool) | ||
|
||
_, err := config.clientLogging.Projects.Sinks.Patch(d.Id(), &sink).UniqueWriterIdentity(uniqueWriterIdentity).Do() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return resourceLoggingProjectSinkRead(d, meta) | ||
} | ||
|
||
func resourceLoggingProjectSinkDelete(d *schema.ResourceData, meta interface{}) error { | ||
config := meta.(*Config) | ||
|
||
_, err := config.clientLogging.Projects.Sinks.Delete(d.Id()).Do() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
d.SetId("") | ||
return nil | ||
} |
Oops, something went wrong.