diff --git a/examples/alias/basic_example.tf b/examples/alias/basic_example.tf new file mode 100644 index 00000000..83fa295e --- /dev/null +++ b/examples/alias/basic_example.tf @@ -0,0 +1,17 @@ +resource "gsuite_user" "test" { + name = { + family_name = "TestAcc_replaceWithUuid" + given_name = "Test" + } + + primary_email = "test_replaceWithUuid@domain.ext" + + lifecycle { + ignore = [aliases] + } +} + +resource "gsuite_user_alias" "test" { + user_id = gsuite_user.test.primary_email + alias = "test-alias-replaceWithUuid@domain.ext" +} diff --git a/go.mod b/go.mod index e81d61fe..0a32dfe4 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.15 require ( cloud.google.com/go v0.79.0 + github.com/cenkalti/backoff/v4 v4.1.1 github.com/golang/protobuf v1.5.2 // indirect github.com/hashicorp/terraform-plugin-sdk v1.13.0 github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index 1575aa92..56ea57d9 100644 --- a/go.sum +++ b/go.sum @@ -60,6 +60,8 @@ github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1U github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ= +github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= diff --git a/gsuite/provider.go b/gsuite/provider.go index 9306246a..3e17a2c3 100644 --- a/gsuite/provider.go +++ b/gsuite/provider.go @@ -61,6 +61,7 @@ func Provider() *schema.Provider { "gsuite_group_members": resourceGroupMembers(), "gsuite_group_settings": resourceGroupSettings(), "gsuite_user": resourceUser(), + "gsuite_user_alias": resourceUserAlias(), "gsuite_user_attributes": resourceUserAttributes(), "gsuite_user_schema": resourceUserSchema(), }, diff --git a/gsuite/resource_user_alias.go b/gsuite/resource_user_alias.go new file mode 100644 index 00000000..434740d7 --- /dev/null +++ b/gsuite/resource_user_alias.go @@ -0,0 +1,153 @@ +package gsuite + +import ( + "fmt" + "log" + "reflect" + "strings" + "time" + + "github.com/cenkalti/backoff/v4" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + admin "google.golang.org/api/admin/directory/v1" +) + +func resourceUserAlias() *schema.Resource { + return &schema.Resource{ + Create: resourceUserAliasCreate, + Read: resourceUserAliasRead, + Update: nil, + Delete: resourceUserAliasDelete, + Importer: &schema.ResourceImporter{ + State: resourceUserAliasImport, + }, + Schema: map[string]*schema.Schema{ + "user_id": { + Type: schema.TypeString, + Description: "ID (userKey) of the user the alias should be applied to.", + Required: true, + ForceNew: true, + }, + "alias": { + Type: schema.TypeString, + Description: "Email alias which should be applied to the user.", + Required: true, + ForceNew: true, + ValidateFunc: validateEmail, + }, + }, + } +} + +func resourceUserAliasCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + userId := d.Get("user_id").(string) + setAlias := d.Get("alias").(string) + + alias := &admin.Alias{ + Alias: setAlias, + } + resp, err := config.directory.Users.Aliases.Insert(userId, alias).Do() + if err != nil { + return fmt.Errorf("[ERROR] failed to add alias for user (%s): %v", userId, err) + } + + bOff := backoff.NewExponentialBackOff() + bOff.MaxElapsedTime = time.Duration(config.TimeoutMinutes) * time.Minute + bOff.InitialInterval = time.Second + + err = backoff.Retry(func() error { + resp, err := config.directory.Users.Aliases.List(userId).Do() + if err != nil { + return backoff.Permanent(fmt.Errorf("[ERROR] could not retrieve aliases for user (%s): %v", userId, err)) + } + + _, ok := doesAliasExist(resp, setAlias) + if ok { + return nil + } + return fmt.Errorf(fmt.Sprintf("[WARN] no matching alias (%s) found for user (%s).", setAlias, userId)) + + }, bOff) + + d.SetId(fmt.Sprintf("%s/%s", resp.PrimaryEmail, resp.Alias)) + return resourceUserAliasRead(d, meta) +} + +func resourceUserAliasRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + userId := d.Get("user_id").(string) + expectedAlias := d.Get("alias").(string) + + resp, err := config.directory.Users.Aliases.List(userId).Do() + if err != nil { + return fmt.Errorf("[ERROR] could not retrieve aliases for user (%s): %v", userId, err) + } + + alias, ok := doesAliasExist(resp, expectedAlias) + if !ok { + log.Println(fmt.Sprintf("[WARN] no matching alias (%s) found for user (%s).", expectedAlias, userId)) + d.SetId("") + return nil + } + d.SetId(fmt.Sprintf("%s/%s", userId, alias)) + d.Set("user_id", userId) + d.Set("alias", alias) + return nil +} + +func resourceUserAliasDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + userId := d.Get("user_id").(string) + alias := d.Get("alias").(string) + + err := config.directory.Users.Aliases.Delete(userId, alias).Do() + if err != nil { + return fmt.Errorf("[ERROR] unable to remove alias (%s) from user (%s): %v", alias, userId, err) + } + + d.SetId("") + return nil +} + +func resourceUserAliasImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + + userId := strings.Split(d.Id(), "/")[0] + expectedAlias := strings.Split(d.Id(), "/")[1] + + resp, err := config.directory.Users.Aliases.List(userId).Do() + if err != nil { + return nil, fmt.Errorf("[ERROR] could not retrieve aliases for user (%s): %v", userId, err) + } + + alias, ok := doesAliasExist(resp, expectedAlias) + if !ok { + return nil, fmt.Errorf("[ERROR] no matching alias (%s) found for user (%s).", expectedAlias, userId) + } + d.SetId(fmt.Sprintf("%s/%s", userId, alias)) + d.Set("user_id", userId) + d.Set("alias", alias) + + return []*schema.ResourceData{d}, nil +} + +func doesAliasExist(aliasesResp *admin.Aliases, expectedAlias string) (string, bool) { + for _, aliasInt := range aliasesResp.Aliases { + alias, ok := aliasInt.(map[string]interface{}) + if ok { + value := alias["alias"].(string) + if expectedAlias == alias["alias"].(string) { + return value, true + } + } + if !ok { + log.Println(fmt.Sprintf("[ERROR] alias format in response did not match sdk struct, this indicates a probelm with provider or sdk handling: %v", reflect.TypeOf(aliasInt))) + return "", false + } + } + return "", false +} diff --git a/website/docs/r/user_alias.md b/website/docs/r/user_alias.md new file mode 100644 index 00000000..b9399bc8 --- /dev/null +++ b/website/docs/r/user_alias.md @@ -0,0 +1,39 @@ +--- +layout: "gsuite" +page_title: "G Suite: gsuite_alias" +sidebar_current: "docs-gsuite-resource-user-alias" +description: |- + Managing a G Suite User Alias. +--- + +# gsuite_user_alias + +Provides a resource for creating and managing an email alias for a GSuite user account. + +## Example Usage + +```hcl +resource "gsuite_user_alias" "test" { + user_id = "test-user-replaceWithUuid@domain.ext" + alias = "test-alias-replaceWithUuid@domain.ext" +} +``` + +## Argument Reference + +* `user_id` (Required) Primary email (userKey) of the user who will have the alias applied to them. +* `alias` (Required) Email alias to be applied to the user. + + +## Attribute Reference + +N/A apart from the included arguments + +## Import + +An alias can be imported by passing the ID format of "{user_id}/{alias}" + +For example: +``` +terraform import gsuite_user_alias.test "test-user@domain.ext/test-alias@domain.ext" +```