From c7b7dace742b800b306bf17d3c520bac7637fa5e Mon Sep 17 00:00:00 2001 From: Alex Pilon Date: Fri, 23 Jul 2021 17:33:09 -0400 Subject: [PATCH 1/9] WIP --- .../provider/data_source_privileges_test.go | 2 +- internal/provider/provider.go | 24 +- internal/provider/provider_config.go | 22 ++ .../provider/resource_gmail_send_as_alias.go | 269 ++++++++++++++++++ internal/provider/services.go | 18 ++ 5 files changed, 323 insertions(+), 12 deletions(-) create mode 100644 internal/provider/resource_gmail_send_as_alias.go diff --git a/internal/provider/data_source_privileges_test.go b/internal/provider/data_source_privileges_test.go index 46421a84..db88b2fd 100644 --- a/internal/provider/data_source_privileges_test.go +++ b/internal/provider/data_source_privileges_test.go @@ -20,7 +20,7 @@ func TestAccDataSourcePrivileges_basic(t *testing.T) { Config: testAccDataSourcePrivileges(), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrSet("data.googleworkspace_privileges.test", "etag"), - resource.TestCheckResourceAttr("data.googleworkspace_privileges.test", "items.#", "104"), + resource.TestCheckResourceAttr("data.googleworkspace_privileges.test", "items.#", "107"), ), }, }, diff --git a/internal/provider/provider.go b/internal/provider/provider.go index cecec6f1..448c1749 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -14,6 +14,7 @@ import ( ) var DefaultClientScopes = []string{ + "https://www.googleapis.com/auth/gmail.settings.sharing", "https://www.googleapis.com/auth/chrome.management.policy", "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/admin.directory.customer", @@ -101,17 +102,18 @@ func New(version string) func() *schema.Provider { "googleworkspace_user": dataSourceUser(), }, ResourcesMap: map[string]*schema.Resource{ - "googleworkspace_chrome_policy": resourceChromePolicy(), - "googleworkspace_domain": resourceDomain(), - "googleworkspace_domain_alias": resourceDomainAlias(), - "googleworkspace_group": resourceGroup(), - "googleworkspace_group_member": resourceGroupMember(), - "googleworkspace_group_settings": resourceGroupSettings(), - "googleworkspace_org_unit": resourceOrgUnit(), - "googleworkspace_role": resourceRole(), - "googleworkspace_role_assignment": resourceRoleAssignment(), - "googleworkspace_schema": resourceSchema(), - "googleworkspace_user": resourceUser(), + "googleworkspace_chrome_policy": resourceChromePolicy(), + "googleworkspace_domain": resourceDomain(), + "googleworkspace_domain_alias": resourceDomainAlias(), + "googleworkspace_gmail_send_as_alias": resourceGmailSendAsAlias(), + "googleworkspace_group": resourceGroup(), + "googleworkspace_group_member": resourceGroupMember(), + "googleworkspace_group_settings": resourceGroupSettings(), + "googleworkspace_org_unit": resourceOrgUnit(), + "googleworkspace_role": resourceRole(), + "googleworkspace_role_assignment": resourceRoleAssignment(), + "googleworkspace_schema": resourceSchema(), + "googleworkspace_user": resourceUser(), }, } diff --git a/internal/provider/provider_config.go b/internal/provider/provider_config.go index 363ec4d8..05cf1fba 100644 --- a/internal/provider/provider_config.go +++ b/internal/provider/provider_config.go @@ -12,6 +12,7 @@ import ( "golang.org/x/oauth2" googleoauth "golang.org/x/oauth2/google" "google.golang.org/api/chromepolicy/v1" + "google.golang.org/api/gmail/v1" "google.golang.org/api/option" directory "google.golang.org/api/admin/directory/v1" @@ -108,6 +109,27 @@ func (c *apiClient) NewDirectoryService() (*directory.Service, diag.Diagnostics) return directoryService, diags } +func (c *apiClient) NewGmailService() (*gmail.Service, diag.Diagnostics) { + var diags diag.Diagnostics + + log.Printf("[INFO] Instantiating Google Admin Gmail service") + + gmailService, err := gmail.NewService(context.Background(), option.WithHTTPClient(c.client)) + if err != nil { + return nil, diag.FromErr(err) + } + + if gmailService == nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Gmail Service could not be created.", + }) + + return nil, diags + } + + return gmailService, diags +} func (c *apiClient) NewGroupsSettingsService() (*groupssettings.Service, diag.Diagnostics) { var diags diag.Diagnostics diff --git a/internal/provider/resource_gmail_send_as_alias.go b/internal/provider/resource_gmail_send_as_alias.go new file mode 100644 index 00000000..62a520e8 --- /dev/null +++ b/internal/provider/resource_gmail_send_as_alias.go @@ -0,0 +1,269 @@ +package googleworkspace + +import ( + "context" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "google.golang.org/api/gmail/v1" +) + +// TODO ensure a safe character is chosen to separate emails for a composite id, +// otherwise it will be difficult to support import +const sendAsIdSeparator = ":" + +func resourceGmailSendAsAlias() *schema.Resource { + return &schema.Resource{ + Description: "Role resource in the Terraform Googleworkspace provider.", + + CreateContext: resourceGmailSendAsAliasCreate, + ReadContext: resourceGmailSendAsAliasRead, + UpdateContext: resourceGmailSendAsAliasUpdate, + DeleteContext: resourceGmailSendAsAliasDelete, + + // TODO split email:sendAs id, is ':' a safe separator? + // Importer: &schema.ResourceImporter{ + // StateContext: nil, + // }, + + Schema: map[string]*schema.Schema{ + "primary_email": { + Description: "User's primary email address.", + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "send_as_email": { + Description: "The email address that appears in the 'From:' header for mail sent using this alias.", + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "display_name": { + Description: "A name that appears in the 'From:' header for mail sent using this alias. For custom 'from' addresses, when this is empty, Gmail will populate the 'From:' header with the name that is used for the primary address associated with the account. If the admin has disabled the ability for users to update their name format, requests to update this field for the primary login will silently fail.", + Type: schema.TypeString, + Optional: true, + }, + "reply_to_address": { + Description: "An optional email address that is included in a 'Reply-To:' header for mail sent using this alias. If this is empty, Gmail will not generate a 'Reply-To:' header.", + Type: schema.TypeString, + Optional: true, + }, + "signature": { + Description: "An optional HTML signature that is included in messages composed with this alias in the Gmail web UI. This signature is added to new emails only.", + Type: schema.TypeString, + Optional: true, + }, + "is_primary": { + Description: "Whether this address is the primary address used to login to the account. Every Gmail account has exactly one primary address, and it cannot be deleted from the collection of send-as aliases.", + Type: schema.TypeBool, + Computed: true, + }, + "is_default": { + Description: "Whether this address is selected as the default 'From:' address in situations such as composing a new message or sending a vacation auto-reply. Every Gmail account has exactly one default send-as address, so the only legal value that clients may write to this field is true. Changing this from false to true for an address will result in this field becoming false for the other previous default address. To avoid drift with Terraform, please change the previous default's config to false after this change is applied and perform a refresh to synchronize with remote state.", + Type: schema.TypeBool, + Optional: true, + }, + "treat_as_alias": { + Description: "Whether Gmail should treat this address as an alias for the user's primary email address. This setting only applies to custom 'from' aliases. See https://support.google.com/a/answer/1710338 for help on making this decision", + Type: schema.TypeBool, + Optional: true, + Default: true, // mirrors the UI + }, + "smtp_msa": { + Description: "An optional SMTP service that will be used as an outbound relay for mail sent using this alias. If this is empty, outbound mail will be sent directly from Gmail's servers to the destination SMTP service. This setting only applies to custom 'from' aliases.", + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "host": { + Description: "The hostname of the SMTP service.", + Type: schema.TypeString, + Required: true, + }, + "port": { + Description: "The port of the SMTP service.", + Type: schema.TypeInt, + Required: true, + }, + "username": { + Description: "The username that will be used for authentication with the SMTP service. This is a write-only field that can be specified in requests to create or update SendAs settings; it is never populated in responses.", + Type: schema.TypeString, + Optional: true, + RequiredWith: []string{"smtp_msa.0.password"}, + }, + "password": { + Description: "The password that will be used for authentication with the SMTP service. This is a write-only field that can be specified in requests to create or update SendAs settings; it is never populated in responses.", + Type: schema.TypeString, + Optional: true, + Sensitive: true, + }, + "security_mode": { + Description: "he protocol that will be used to secure communication with the SMTP service.", + Type: schema.TypeString, + Optional: true, + Default: "securityModeUnspecified", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"securityModeUnspecified", "none", "ssl", "starttls"}, false)), + }, + }, + }, + }, + "verification_status": { + Description: "Indicates whether this address has been verified for use as a send-as alias.", + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceGmailSendAsAliasCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*apiClient) + + gmailService, diags := client.NewGmailService() + if diags.HasError() { + return diags + } + + sendAsAliasService, diags := GetGmailSendAsAliasService(gmailService) + if diags.HasError() { + return diags + } + + primaryEmail := d.Get("primary_email").(string) + sendAsEmail := d.Get("send_as_email").(string) + log.Printf("[DEBUG] Creating Gmail Send As Alias %q", primaryEmail+sendAsIdSeparator+sendAsEmail) + + sendAs, err := sendAsAliasService.Create(primaryEmail, &gmail.SendAs{ + SendAsEmail: sendAsEmail, + DisplayName: d.Get("display_name").(string), + ReplyToAddress: d.Get("reply_to_address").(string), + Signature: d.Get("signature").(string), + IsDefault: d.Get("is_default").(bool), + TreatAsAlias: d.Get("treat_as_alias").(bool), + SmtpMsa: expandSmtpMsa(d.Get("smtp_msa").([]interface{})), + }).Do() + if err != nil { + diag.FromErr(err) + } + + d.Set("send_as_email", sendAs.SendAsEmail) + d.SetId(primaryEmail + sendAsIdSeparator + sendAs.SendAsEmail) + + log.Printf("[DEBUG] Finished creating Gmail Send As Alias %q", d.Id()) + + return resourceGmailSendAsAliasRead(ctx, d, meta) +} + +func resourceGmailSendAsAliasUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*apiClient) + + gmailService, diags := client.NewGmailService() + if diags.HasError() { + return diags + } + + sendAsAliasService, diags := GetGmailSendAsAliasService(gmailService) + if diags.HasError() { + return diags + } + + log.Printf("[DEBUG] Updating Gmail Send As Alias %q", d.Id()) + + log.Printf("[DEBUG] Finished updating Gmail Send As Alias %q", d.Id()) + + return resourceGmailSendAsAliasRead(ctx, d, meta) +} + +func resourceGmailSendAsAliasRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*apiClient) + + gmailService, diags := client.NewGmailService() + if diags.HasError() { + return diags + } + + sendAsAliasService, diags := GetGmailSendAsAliasService(gmailService) + if diags.HasError() { + return diags + } + + log.Printf("[DEBUG] Getting Gmail Send As Alias %q", d.Id()) + + primaryEmail := d.Get("primary_email").(string) + sendAs, err := sendAsAliasService.Get(primaryEmail, d.Get("send_as_email").(string)).Do() + if err != nil { + return diag.FromErr(err) + } + + log.Printf("[DEBUG] Finished getting Gmail Send As Alias %q", d.Id()) + + d.SetId(primaryEmail + sendAsIdSeparator + sendAs.SendAsEmail) + d.Set("send_as_email", sendAs.SendAsEmail) + d.Set("display_name", sendAs.DisplayName) + d.Set("reply_to_address", sendAs.ReplyToAddress) + d.Set("signature", sendAs.Signature) + d.Set("is_primary", sendAs.IsPrimary) + d.Set("is_default", sendAs.IsDefault) + d.Set("treat_as_alias", sendAs.TreatAsAlias) + d.Set("verification_status", sendAs.VerificationStatus) + if sendAs.SmtpMsa != nil { + if err := d.Set("smtp_msa", flattenSmtpMsa(sendAs.SmtpMsa, d)); err != nil { + return diag.FromErr(err) + } + } + + return nil +} + +func resourceGmailSendAsAliasDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*apiClient) + + gmailService, diags := client.NewGmailService() + if diags.HasError() { + return diags + } + + sendAsAliasService, diags := GetGmailSendAsAliasService(gmailService) + if diags.HasError() { + return diags + } + + log.Printf("[DEBUG] Deleting Gmail Send As Alias %q", d.Id()) + + log.Printf("[DEBUG] Finished deleting Gmail Send As Alias %q", d.Id()) + + return nil +} + +func expandSmtpMsa(smtpMsa []interface{}) *gmail.SmtpMsa { + if len(smtpMsa) == 0 { + return nil + } + values := smtpMsa[0].(map[string]interface{}) + return &gmail.SmtpMsa{ + Host: values["host"].(string), + Port: int64(values["port"].(int)), + Username: values["username"].(string), + Password: values["password"].(string), + SecurityMode: values["security_mode"].(string), + } +} + +func flattenSmtpMsa(smtpMsa *gmail.SmtpMsa, d *schema.ResourceData) []interface{} { + result := make(map[string]interface{}) + + // need to retrieve username/password from config + configSmtpMsa := expandSmtpMsa(d.Get("smtp_msa").([]interface{})) + + result["host"] = smtpMsa.Host + result["port"] = int(smtpMsa.Port) + result["security_mode"] = smtpMsa.SecurityMode + result["username"] = configSmtpMsa.Username + result["password"] = configSmtpMsa.Password + + return []interface{}{result} +} diff --git a/internal/provider/services.go b/internal/provider/services.go index 37fa95e0..1af80f6a 100644 --- a/internal/provider/services.go +++ b/internal/provider/services.go @@ -7,6 +7,7 @@ import ( directory "google.golang.org/api/admin/directory/v1" "google.golang.org/api/chromepolicy/v1" + "google.golang.org/api/gmail/v1" "google.golang.org/api/groupssettings/v1" ) @@ -112,6 +113,23 @@ func GetGroupsSettingsService(groupsSettingsService *groupssettings.Service) (*g return groupsService, diags } +func GetGmailSendAsAliasService(gmailService *gmail.Service) (*gmail.UsersSettingsSendAsService, diag.Diagnostics) { + var diags diag.Diagnostics + + log.Printf("[INFO] Instantiating Google Admin Gmail Send As Alias service") + usersService := gmailService.Users + if usersService == nil || usersService.Settings == nil || usersService.Settings.SendAs == nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Send As Alias Service could not be created.", + }) + + return nil, diags + } + + return usersService.Settings.SendAs, diags +} + func GetGroupAliasService(groupsService *directory.GroupsService) (*directory.GroupsAliasesService, diag.Diagnostics) { var diags diag.Diagnostics From 4ff669b183ff88268ace9fa60e91865b0d6a1636 Mon Sep 17 00:00:00 2001 From: Alex Pilon Date: Fri, 23 Jul 2021 17:37:58 -0400 Subject: [PATCH 2/9] Implement delete --- internal/provider/resource_gmail_send_as_alias.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/provider/resource_gmail_send_as_alias.go b/internal/provider/resource_gmail_send_as_alias.go index 62a520e8..74a0aa89 100644 --- a/internal/provider/resource_gmail_send_as_alias.go +++ b/internal/provider/resource_gmail_send_as_alias.go @@ -234,6 +234,11 @@ func resourceGmailSendAsAliasDelete(ctx context.Context, d *schema.ResourceData, log.Printf("[DEBUG] Deleting Gmail Send As Alias %q", d.Id()) + err := sendAsAliasService.Delete(d.Get("primary_email").(string), d.Get("send_as_email").(string)).Do() + if err != nil { + handleNotFoundError(err, d, d.Id()) + } + log.Printf("[DEBUG] Finished deleting Gmail Send As Alias %q", d.Id()) return nil From fedc97cd168b53e58ead414d4f39ac9c6a219d70 Mon Sep 17 00:00:00 2001 From: Alex Pilon Date: Fri, 23 Jul 2021 17:39:25 -0400 Subject: [PATCH 3/9] handle not found in sendAs read --- internal/provider/resource_gmail_send_as_alias.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provider/resource_gmail_send_as_alias.go b/internal/provider/resource_gmail_send_as_alias.go index 74a0aa89..56c94e28 100644 --- a/internal/provider/resource_gmail_send_as_alias.go +++ b/internal/provider/resource_gmail_send_as_alias.go @@ -196,7 +196,7 @@ func resourceGmailSendAsAliasRead(ctx context.Context, d *schema.ResourceData, m primaryEmail := d.Get("primary_email").(string) sendAs, err := sendAsAliasService.Get(primaryEmail, d.Get("send_as_email").(string)).Do() if err != nil { - return diag.FromErr(err) + return handleNotFoundError(err, d, d.Id()) } log.Printf("[DEBUG] Finished getting Gmail Send As Alias %q", d.Id()) From ae2777b54c613c92ec43d384120a2f7505740873 Mon Sep 17 00:00:00 2001 From: Alex Pilon Date: Mon, 26 Jul 2021 15:29:07 -0400 Subject: [PATCH 4/9] Finish implementation --- internal/provider/provider.go | 3 + .../provider/resource_gmail_send_as_alias.go | 31 ++++++++-- .../resource_gmail_send_as_alias_test.go | 59 +++++++++++++++++++ 3 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 internal/provider/resource_gmail_send_as_alias_test.go diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 448c1749..0a6ed874 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -14,6 +14,9 @@ import ( ) var DefaultClientScopes = []string{ + "https://www.googleapis.com/auth/gmail.modify", + "https://www.googleapis.com/auth/gmail.readonly", + "https://www.googleapis.com/auth/gmail.settings.basic", "https://www.googleapis.com/auth/gmail.settings.sharing", "https://www.googleapis.com/auth/chrome.management.policy", "https://www.googleapis.com/auth/cloud-platform", diff --git a/internal/provider/resource_gmail_send_as_alias.go b/internal/provider/resource_gmail_send_as_alias.go index 56c94e28..41ea4ae2 100644 --- a/internal/provider/resource_gmail_send_as_alias.go +++ b/internal/provider/resource_gmail_send_as_alias.go @@ -2,7 +2,9 @@ package googleworkspace import ( "context" + "fmt" "log" + "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -23,10 +25,17 @@ func resourceGmailSendAsAlias() *schema.Resource { UpdateContext: resourceGmailSendAsAliasUpdate, DeleteContext: resourceGmailSendAsAliasDelete, - // TODO split email:sendAs id, is ':' a safe separator? - // Importer: &schema.ResourceImporter{ - // StateContext: nil, - // }, + Importer: &schema.ResourceImporter{ + StateContext: func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + idParts := strings.Split(d.Id(), sendAsIdSeparator) + if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { + return nil, fmt.Errorf("Unexpected format of ID (%q), expected primary-email%ssend-as-email", d.Id(), sendAsIdSeparator) + } + d.Set("primary_email", idParts[0]) + d.Set("send_as_email", idParts[1]) + return []*schema.ResourceData{d}, nil + }, + }, Schema: map[string]*schema.Schema{ "primary_email": { @@ -147,7 +156,7 @@ func resourceGmailSendAsAliasCreate(ctx context.Context, d *schema.ResourceData, SmtpMsa: expandSmtpMsa(d.Get("smtp_msa").([]interface{})), }).Do() if err != nil { - diag.FromErr(err) + return diag.FromErr(err) } d.Set("send_as_email", sendAs.SendAsEmail) @@ -173,6 +182,18 @@ func resourceGmailSendAsAliasUpdate(ctx context.Context, d *schema.ResourceData, log.Printf("[DEBUG] Updating Gmail Send As Alias %q", d.Id()) + _, err := sendAsAliasService.Update(d.Get("primary_email").(string), d.Get("send_as_email").(string), &gmail.SendAs{ + DisplayName: d.Get("display_name").(string), + ReplyToAddress: d.Get("reply_to_address").(string), + Signature: d.Get("signature").(string), + IsDefault: d.Get("is_default").(bool), + TreatAsAlias: d.Get("treat_as_alias").(bool), + SmtpMsa: expandSmtpMsa(d.Get("smtp_msa").([]interface{})), + }).Do() + if err != nil { + return diag.FromErr(err) + } + log.Printf("[DEBUG] Finished updating Gmail Send As Alias %q", d.Id()) return resourceGmailSendAsAliasRead(ctx, d, meta) diff --git a/internal/provider/resource_gmail_send_as_alias_test.go b/internal/provider/resource_gmail_send_as_alias_test.go new file mode 100644 index 00000000..06b04076 --- /dev/null +++ b/internal/provider/resource_gmail_send_as_alias_test.go @@ -0,0 +1,59 @@ +package googleworkspace + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccResourceGmailSendAsAlias_basic(t *testing.T) { + t.Parallel() + + domainName := os.Getenv("GOOGLEWORKSPACE_DOMAIN") + + if domainName == "" { + t.Skip("GOOGLEWORKSPACE_DOMAIN needs to be set to run this test") + } + data := map[string]interface{}{ + "domainName": domainName, + "userEmail": fmt.Sprintf("tf-test-%s", acctest.RandString(10)), + "password": acctest.RandString(10), + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: testAccGmailSendAsAlias_basic(data), + }, + { + ResourceName: "googleworkspace_gmail_send_as_alias.test", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccGmailSendAsAlias_basic(data map[string]interface{}) string { + return Nprintf(` +resource "googleworkspace_user" "test" { + primary_email = "%{userEmail}@%{domainName}" + password = "%{password}" + + name { + family_name = "Scott" + given_name = "Michael" + } +} + +resource "googleworkspace_gmail_send_as_alias" "test" { + primary_email = googleworkspace_user.test.primary_email + send_as_email = "test@test.com" +} +`, data) +} From 975d8a331142e0233947d24f84e6cf7c63233b87 Mon Sep 17 00:00:00 2001 From: Alex Pilon Date: Mon, 26 Jul 2021 16:40:47 -0400 Subject: [PATCH 5/9] Fix implementation --- internal/provider/provider.go | 2 -- internal/provider/provider_config.go | 19 ++++++++++++++-- .../provider/resource_gmail_send_as_alias.go | 22 ++++++++++--------- .../resource_gmail_send_as_alias_test.go | 18 ++++++++++++--- 4 files changed, 44 insertions(+), 17 deletions(-) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 0a6ed874..2b228fed 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -14,8 +14,6 @@ import ( ) var DefaultClientScopes = []string{ - "https://www.googleapis.com/auth/gmail.modify", - "https://www.googleapis.com/auth/gmail.readonly", "https://www.googleapis.com/auth/gmail.settings.basic", "https://www.googleapis.com/auth/gmail.settings.sharing", "https://www.googleapis.com/auth/chrome.management.policy", diff --git a/internal/provider/provider_config.go b/internal/provider/provider_config.go index 05cf1fba..06b9826a 100644 --- a/internal/provider/provider_config.go +++ b/internal/provider/provider_config.go @@ -109,12 +109,27 @@ func (c *apiClient) NewDirectoryService() (*directory.Service, diag.Diagnostics) return directoryService, diags } -func (c *apiClient) NewGmailService() (*gmail.Service, diag.Diagnostics) { +func (c *apiClient) NewGmailService(ctx context.Context, userId string) (*gmail.Service, diag.Diagnostics) { var diags diag.Diagnostics log.Printf("[INFO] Instantiating Google Admin Gmail service") - gmailService, err := gmail.NewService(context.Background(), option.WithHTTPClient(c.client)) + // the send-as-alias resource requires the oauth token impersonate the user + // the alias is being created for. + log.Printf("[INFO] Creating Google Admin Gmail client that impersonates %q", userId) + newClient := &apiClient{ + Credentials: c.Credentials, + ClientScopes: c.ClientScopes, + Customer: c.Customer, + UserAgent: c.UserAgent, + ImpersonatedUserEmail: userId, + } + diags = newClient.loadAndValidate(ctx) + if diags.HasError() { + return nil, diags + } + + gmailService, err := gmail.NewService(ctx, option.WithHTTPClient(newClient.client)) if err != nil { return nil, diag.FromErr(err) } diff --git a/internal/provider/resource_gmail_send_as_alias.go b/internal/provider/resource_gmail_send_as_alias.go index 41ea4ae2..6b5a12d0 100644 --- a/internal/provider/resource_gmail_send_as_alias.go +++ b/internal/provider/resource_gmail_send_as_alias.go @@ -132,7 +132,8 @@ func resourceGmailSendAsAlias() *schema.Resource { func resourceGmailSendAsAliasCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*apiClient) - gmailService, diags := client.NewGmailService() + primaryEmail := d.Get("primary_email").(string) + gmailService, diags := client.NewGmailService(ctx, primaryEmail) if diags.HasError() { return diags } @@ -142,11 +143,10 @@ func resourceGmailSendAsAliasCreate(ctx context.Context, d *schema.ResourceData, return diags } - primaryEmail := d.Get("primary_email").(string) sendAsEmail := d.Get("send_as_email").(string) log.Printf("[DEBUG] Creating Gmail Send As Alias %q", primaryEmail+sendAsIdSeparator+sendAsEmail) - sendAs, err := sendAsAliasService.Create(primaryEmail, &gmail.SendAs{ + sendAs, err := sendAsAliasService.Create("me", &gmail.SendAs{ SendAsEmail: sendAsEmail, DisplayName: d.Get("display_name").(string), ReplyToAddress: d.Get("reply_to_address").(string), @@ -170,7 +170,8 @@ func resourceGmailSendAsAliasCreate(ctx context.Context, d *schema.ResourceData, func resourceGmailSendAsAliasUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*apiClient) - gmailService, diags := client.NewGmailService() + primaryEmail := d.Get("primary_email").(string) + gmailService, diags := client.NewGmailService(ctx, primaryEmail) if diags.HasError() { return diags } @@ -182,7 +183,7 @@ func resourceGmailSendAsAliasUpdate(ctx context.Context, d *schema.ResourceData, log.Printf("[DEBUG] Updating Gmail Send As Alias %q", d.Id()) - _, err := sendAsAliasService.Update(d.Get("primary_email").(string), d.Get("send_as_email").(string), &gmail.SendAs{ + _, err := sendAsAliasService.Update("me", d.Get("send_as_email").(string), &gmail.SendAs{ DisplayName: d.Get("display_name").(string), ReplyToAddress: d.Get("reply_to_address").(string), Signature: d.Get("signature").(string), @@ -202,7 +203,8 @@ func resourceGmailSendAsAliasUpdate(ctx context.Context, d *schema.ResourceData, func resourceGmailSendAsAliasRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*apiClient) - gmailService, diags := client.NewGmailService() + primaryEmail := d.Get("primary_email").(string) + gmailService, diags := client.NewGmailService(ctx, primaryEmail) if diags.HasError() { return diags } @@ -214,8 +216,7 @@ func resourceGmailSendAsAliasRead(ctx context.Context, d *schema.ResourceData, m log.Printf("[DEBUG] Getting Gmail Send As Alias %q", d.Id()) - primaryEmail := d.Get("primary_email").(string) - sendAs, err := sendAsAliasService.Get(primaryEmail, d.Get("send_as_email").(string)).Do() + sendAs, err := sendAsAliasService.Get("me", d.Get("send_as_email").(string)).Do() if err != nil { return handleNotFoundError(err, d, d.Id()) } @@ -243,7 +244,8 @@ func resourceGmailSendAsAliasRead(ctx context.Context, d *schema.ResourceData, m func resourceGmailSendAsAliasDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*apiClient) - gmailService, diags := client.NewGmailService() + primaryEmail := d.Get("primary_email").(string) + gmailService, diags := client.NewGmailService(ctx, primaryEmail) if diags.HasError() { return diags } @@ -255,7 +257,7 @@ func resourceGmailSendAsAliasDelete(ctx context.Context, d *schema.ResourceData, log.Printf("[DEBUG] Deleting Gmail Send As Alias %q", d.Id()) - err := sendAsAliasService.Delete(d.Get("primary_email").(string), d.Get("send_as_email").(string)).Do() + err := sendAsAliasService.Delete("me", d.Get("send_as_email").(string)).Do() if err != nil { handleNotFoundError(err, d, d.Id()) } diff --git a/internal/provider/resource_gmail_send_as_alias_test.go b/internal/provider/resource_gmail_send_as_alias_test.go index 06b04076..72d0251c 100644 --- a/internal/provider/resource_gmail_send_as_alias_test.go +++ b/internal/provider/resource_gmail_send_as_alias_test.go @@ -12,15 +12,23 @@ import ( func TestAccResourceGmailSendAsAlias_basic(t *testing.T) { t.Parallel() + gmailUser := os.Getenv("GOOGLEWORKSPACE_TEST_GMAIL_USER") + + if gmailUser == "" { + t.Skip("GOOGLEWORKSPACE_TEST_GMAIL_USER needs to be set to run this test") + } + domainName := os.Getenv("GOOGLEWORKSPACE_DOMAIN") if domainName == "" { t.Skip("GOOGLEWORKSPACE_DOMAIN needs to be set to run this test") } + data := map[string]interface{}{ "domainName": domainName, "userEmail": fmt.Sprintf("tf-test-%s", acctest.RandString(10)), "password": acctest.RandString(10), + "gmailUser": gmailUser, } resource.Test(t, resource.TestCase{ @@ -41,7 +49,11 @@ func TestAccResourceGmailSendAsAlias_basic(t *testing.T) { func testAccGmailSendAsAlias_basic(data map[string]interface{}) string { return Nprintf(` -resource "googleworkspace_user" "test" { +data "googleworkspace_user" "test" { + primary_email = "%{gmailUser}" +} + +resource "googleworkspace_user" "alias" { primary_email = "%{userEmail}@%{domainName}" password = "%{password}" @@ -52,8 +64,8 @@ resource "googleworkspace_user" "test" { } resource "googleworkspace_gmail_send_as_alias" "test" { - primary_email = googleworkspace_user.test.primary_email - send_as_email = "test@test.com" + primary_email = data.googleworkspace_user.test.primary_email + send_as_email = googleworkspace_user.alias.primary_email } `, data) } From a488967fe8d698c2832c0dea425e3ddb9f1dab22 Mon Sep 17 00:00:00 2001 From: Alex Pilon Date: Mon, 26 Jul 2021 18:12:38 -0400 Subject: [PATCH 6/9] Add tests and examples --- docs/resources/gmail_send_as_alias.md | 80 ++++++ .../import.sh | 1 + .../resource.tf | 19 ++ .../provider/resource_gmail_send_as_alias.go | 4 +- .../resource_gmail_send_as_alias_test.go | 252 ++++++++++++++++++ 5 files changed, 354 insertions(+), 2 deletions(-) create mode 100644 docs/resources/gmail_send_as_alias.md create mode 100644 examples/resources/googleworkspace_gmail_send_as_alias/import.sh create mode 100644 examples/resources/googleworkspace_gmail_send_as_alias/resource.tf diff --git a/docs/resources/gmail_send_as_alias.md b/docs/resources/gmail_send_as_alias.md new file mode 100644 index 00000000..4e814dee --- /dev/null +++ b/docs/resources/gmail_send_as_alias.md @@ -0,0 +1,80 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "googleworkspace_gmail_send_as_alias Resource - terraform-provider-googleworkspace" +subcategory: "" +description: |- + Gmail Send As Alias resource in the Terraform Googleworkspace provider. Please ensure the Gmail API is enabled for your workspace and that the user being configured has a Gmail license. +--- + +# googleworkspace_gmail_send_as_alias (Resource) + +Gmail Send As Alias resource in the Terraform Googleworkspace provider. Please ensure the Gmail API is enabled for your workspace and that the user being configured has a Gmail license. + +## Example Usage + +```terraform +data "googleworkspace_user" "example" { + primary_email = "user.with.gmail.license@example.com" +} + +resource "googleworkspace_user" "alias" { + primary_email = "alias@example.com" + password = "34819d7beeabb9260a5c854bc85b3e44" + hash_function = "MD5" + + name { + family_name = "Scott" + given_name = "Michael" + } +} + +resource "googleworkspace_gmail_send_as_alias" "test" { + primary_email = data.googleworkspace_user.example.primary_email + send_as_email = googleworkspace_user.alias.primary_email +} +``` + + +## Schema + +### Required + +- **primary_email** (String) User's primary email address. +- **send_as_email** (String) The email address that appears in the 'From:' header for mail sent using this alias. + +### Optional + +- **display_name** (String) A name that appears in the 'From:' header for mail sent using this alias. For custom 'from' addresses, when this is empty, Gmail will populate the 'From:' header with the name that is used for the primary address associated with the account. If the admin has disabled the ability for users to update their name format, requests to update this field for the primary login will silently fail. +- **id** (String) The ID of this resource. +- **is_default** (Boolean) Whether this address is selected as the default 'From:' address in situations such as composing a new message or sending a vacation auto-reply. Every Gmail account has exactly one default send-as address, so the only legal value that clients may write to this field is true. Changing this from false to true for an address will result in this field becoming false for the other previous default address. Toggling an existing alias' default to false is not possible, another alias must be added/imported and toggled to true to remove the default from an existing alias. To avoid drift with Terraform, please change the previous default's config to false AFTER a new default is applied and perform a refresh to synchronize with remote state. +- **reply_to_address** (String) An optional email address that is included in a 'Reply-To:' header for mail sent using this alias. If this is empty, Gmail will not generate a 'Reply-To:' header. +- **signature** (String) An optional HTML signature that is included in messages composed with this alias in the Gmail web UI. This signature is added to new emails only. +- **smtp_msa** (Block List, Max: 1) An optional SMTP service that will be used as an outbound relay for mail sent using this alias. If this is empty, outbound mail will be sent directly from Gmail's servers to the destination SMTP service. This setting only applies to custom 'from' aliases. (see [below for nested schema](#nestedblock--smtp_msa)) +- **treat_as_alias** (Boolean) Whether Gmail should treat this address as an alias for the user's primary email address. This setting only applies to custom 'from' aliases. See https://support.google.com/a/answer/1710338 for help on making this decision Defaults to `true`. + +### Read-Only + +- **is_primary** (Boolean) Whether this address is the primary address used to login to the account. Every Gmail account has exactly one primary address, and it cannot be deleted from the collection of send-as aliases. +- **verification_status** (String) Indicates whether this address has been verified for use as a send-as alias. + + +### Nested Schema for `smtp_msa` + +Required: + +- **host** (String) The hostname of the SMTP service. +- **port** (Number) The port of the SMTP service. + +Optional: + +- **password** (String, Sensitive) The password that will be used for authentication with the SMTP service. This is a write-only field that can be specified in requests to create or update SendAs settings; it is never populated in responses. +- **security_mode** (String) he protocol that will be used to secure communication with the SMTP service. Defaults to `securityModeUnspecified`. +- **username** (String) The username that will be used for authentication with the SMTP service. This is a write-only field that can be specified in requests to create or update SendAs settings; it is never populated in responses. + +## Import + +Import is supported using the following syntax: + +```shell +terraform import googleworkspace_gmail_send_as_alias.alias user@example.com:alias@anotherexample.com +``` diff --git a/examples/resources/googleworkspace_gmail_send_as_alias/import.sh b/examples/resources/googleworkspace_gmail_send_as_alias/import.sh new file mode 100644 index 00000000..154b959f --- /dev/null +++ b/examples/resources/googleworkspace_gmail_send_as_alias/import.sh @@ -0,0 +1 @@ +terraform import googleworkspace_gmail_send_as_alias.alias user@example.com:alias@anotherexample.com \ No newline at end of file diff --git a/examples/resources/googleworkspace_gmail_send_as_alias/resource.tf b/examples/resources/googleworkspace_gmail_send_as_alias/resource.tf new file mode 100644 index 00000000..1515e31a --- /dev/null +++ b/examples/resources/googleworkspace_gmail_send_as_alias/resource.tf @@ -0,0 +1,19 @@ +data "googleworkspace_user" "example" { + primary_email = "user.with.gmail.license@example.com" +} + +resource "googleworkspace_user" "alias" { + primary_email = "alias@example.com" + password = "34819d7beeabb9260a5c854bc85b3e44" + hash_function = "MD5" + + name { + family_name = "Scott" + given_name = "Michael" + } +} + +resource "googleworkspace_gmail_send_as_alias" "test" { + primary_email = data.googleworkspace_user.example.primary_email + send_as_email = googleworkspace_user.alias.primary_email +} \ No newline at end of file diff --git a/internal/provider/resource_gmail_send_as_alias.go b/internal/provider/resource_gmail_send_as_alias.go index 6b5a12d0..6ee20d0a 100644 --- a/internal/provider/resource_gmail_send_as_alias.go +++ b/internal/provider/resource_gmail_send_as_alias.go @@ -18,7 +18,7 @@ const sendAsIdSeparator = ":" func resourceGmailSendAsAlias() *schema.Resource { return &schema.Resource{ - Description: "Role resource in the Terraform Googleworkspace provider.", + Description: "Gmail Send As Alias resource in the Terraform Googleworkspace provider. Please ensure the Gmail API is enabled for your workspace and that the user being configured has a Gmail license.", CreateContext: resourceGmailSendAsAliasCreate, ReadContext: resourceGmailSendAsAliasRead, @@ -71,7 +71,7 @@ func resourceGmailSendAsAlias() *schema.Resource { Computed: true, }, "is_default": { - Description: "Whether this address is selected as the default 'From:' address in situations such as composing a new message or sending a vacation auto-reply. Every Gmail account has exactly one default send-as address, so the only legal value that clients may write to this field is true. Changing this from false to true for an address will result in this field becoming false for the other previous default address. To avoid drift with Terraform, please change the previous default's config to false after this change is applied and perform a refresh to synchronize with remote state.", + Description: "Whether this address is selected as the default 'From:' address in situations such as composing a new message or sending a vacation auto-reply. Every Gmail account has exactly one default send-as address, so the only legal value that clients may write to this field is true. Changing this from false to true for an address will result in this field becoming false for the other previous default address. Toggling an existing alias' default to false is not possible, another alias must be added/imported and toggled to true to remove the default from an existing alias. To avoid drift with Terraform, please change the previous default's config to false AFTER a new default is applied and perform a refresh to synchronize with remote state.", Type: schema.TypeBool, Optional: true, }, diff --git a/internal/provider/resource_gmail_send_as_alias_test.go b/internal/provider/resource_gmail_send_as_alias_test.go index 72d0251c..6dc97e00 100644 --- a/internal/provider/resource_gmail_send_as_alias_test.go +++ b/internal/provider/resource_gmail_send_as_alias_test.go @@ -3,6 +3,7 @@ package googleworkspace import ( "fmt" "os" + "regexp" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" @@ -47,6 +48,127 @@ func TestAccResourceGmailSendAsAlias_basic(t *testing.T) { }) } +func TestAccResourceGmailSendAsAlias_full(t *testing.T) { + t.Parallel() + + gmailUser := os.Getenv("GOOGLEWORKSPACE_TEST_GMAIL_USER") + + if gmailUser == "" { + t.Skip("GOOGLEWORKSPACE_TEST_GMAIL_USER needs to be set to run this test") + } + + domainName := os.Getenv("GOOGLEWORKSPACE_DOMAIN") + + if domainName == "" { + t.Skip("GOOGLEWORKSPACE_DOMAIN needs to be set to run this test") + } + + data := map[string]interface{}{ + "domainName": domainName, + "userEmail": fmt.Sprintf("tf-test-%s", acctest.RandString(10)), + "password": acctest.RandString(10), + "gmailUser": gmailUser, + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: testAccGmailSendAsAlias_basic(data), + }, + { + ResourceName: "googleworkspace_gmail_send_as_alias.test", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccGmailSendAsAlias_full(data), + }, + { + ResourceName: "googleworkspace_gmail_send_as_alias.test", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccResourceGmailSendAsAlias_defaultCannotBeSetToFalseDirectly(t *testing.T) { + t.Parallel() + + gmailUser := os.Getenv("GOOGLEWORKSPACE_TEST_GMAIL_USER") + + if gmailUser == "" { + t.Skip("GOOGLEWORKSPACE_TEST_GMAIL_USER needs to be set to run this test") + } + + domainName := os.Getenv("GOOGLEWORKSPACE_DOMAIN") + + if domainName == "" { + t.Skip("GOOGLEWORKSPACE_DOMAIN needs to be set to run this test") + } + + data := map[string]interface{}{ + "domainName": domainName, + "userEmail": fmt.Sprintf("tf-test-%s", acctest.RandString(10)), + "password": acctest.RandString(10), + "gmailUser": gmailUser, + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: testAccGmailSendAsAlias_withDefault(data), + }, + { + Config: testAccGmailSendAsAlias_basic(data), + ExpectError: regexp.MustCompile("isDefault cannot be toggled to false"), + }, + }, + }) +} + +func TestAccResourceGmailSendAsAlias_switchingDefaultCausesDiff(t *testing.T) { + t.Parallel() + + gmailUser := os.Getenv("GOOGLEWORKSPACE_TEST_GMAIL_USER") + + if gmailUser == "" { + t.Skip("GOOGLEWORKSPACE_TEST_GMAIL_USER needs to be set to run this test") + } + + domainName := os.Getenv("GOOGLEWORKSPACE_DOMAIN") + + if domainName == "" { + t.Skip("GOOGLEWORKSPACE_DOMAIN needs to be set to run this test") + } + + data := map[string]interface{}{ + "domainName": domainName, + "userEmail": fmt.Sprintf("tf-test-%s", acctest.RandString(10)), + "password": acctest.RandString(10), + "userEmail2": fmt.Sprintf("tf-test-%s", acctest.RandString(10)), + "gmailUser": gmailUser, + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: testAccGmailSendAsAlias_withDefaultUser1(data), + }, + { + Config: testAccGmailSendAsAlias_withDefaultUser2(data), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + func testAccGmailSendAsAlias_basic(data map[string]interface{}) string { return Nprintf(` data "googleworkspace_user" "test" { @@ -69,3 +191,133 @@ resource "googleworkspace_gmail_send_as_alias" "test" { } `, data) } + +func testAccGmailSendAsAlias_full(data map[string]interface{}) string { + return Nprintf(` +data "googleworkspace_user" "test" { + primary_email = "%{gmailUser}" +} + +resource "googleworkspace_user" "alias" { + primary_email = "%{userEmail}@%{domainName}" + password = "%{password}" + + name { + family_name = "Scott" + given_name = "Michael" + } +} + +resource "googleworkspace_gmail_send_as_alias" "test" { + primary_email = data.googleworkspace_user.test.primary_email + send_as_email = googleworkspace_user.alias.primary_email + display_name = "test" + reply_to_address = "test@test.com" + signature = "yours truly" + treat_as_alias = false +} +`, data) +} + +func testAccGmailSendAsAlias_withDefault(data map[string]interface{}) string { + return Nprintf(` +data "googleworkspace_user" "test" { + primary_email = "%{gmailUser}" +} + +resource "googleworkspace_user" "alias" { + primary_email = "%{userEmail}@%{domainName}" + password = "%{password}" + + name { + family_name = "Scott" + given_name = "Michael" + } +} + +resource "googleworkspace_gmail_send_as_alias" "test" { + primary_email = data.googleworkspace_user.test.primary_email + send_as_email = googleworkspace_user.alias.primary_email + is_default = true +} +`, data) +} + +func testAccGmailSendAsAlias_withDefaultUser1(data map[string]interface{}) string { + return Nprintf(` +data "googleworkspace_user" "test" { + primary_email = "%{gmailUser}" +} + +resource "googleworkspace_user" "alias" { + primary_email = "%{userEmail}@%{domainName}" + password = "%{password}" + + name { + family_name = "Scott" + given_name = "Michael" + } +} + +resource "googleworkspace_user" "alias2" { + primary_email = "%{userEmail2}@%{domainName}" + password = "%{password}" + + name { + family_name = "Schrute" + given_name = "Dwight" + } +} + +resource "googleworkspace_gmail_send_as_alias" "test" { + primary_email = data.googleworkspace_user.test.primary_email + send_as_email = googleworkspace_user.alias.primary_email + is_default = true +} + +resource "googleworkspace_gmail_send_as_alias" "test2" { + primary_email = data.googleworkspace_user.test.primary_email + send_as_email = googleworkspace_user.alias2.primary_email +} +`, data) +} + +func testAccGmailSendAsAlias_withDefaultUser2(data map[string]interface{}) string { + return Nprintf(` +data "googleworkspace_user" "test" { + primary_email = "%{gmailUser}" +} + +resource "googleworkspace_user" "alias" { + primary_email = "%{userEmail}@%{domainName}" + password = "%{password}" + + name { + family_name = "Scott" + given_name = "Michael" + } +} + +resource "googleworkspace_user" "alias2" { + primary_email = "%{userEmail2}@%{domainName}" + password = "%{password}" + + name { + family_name = "Schrute" + given_name = "Dwight" + } +} + +resource "googleworkspace_gmail_send_as_alias" "test" { + primary_email = data.googleworkspace_user.test.primary_email + send_as_email = googleworkspace_user.alias.primary_email + is_default = true +} + +resource "googleworkspace_gmail_send_as_alias" "test2" { + primary_email = data.googleworkspace_user.test.primary_email + send_as_email = googleworkspace_user.alias2.primary_email + is_default = true +} +`, data) +} From 88ac131d02db21d801fca52c8960b4b4185435b9 Mon Sep 17 00:00:00 2001 From: Alex Pilon Date: Tue, 27 Jul 2021 10:43:27 -0400 Subject: [PATCH 7/9] Remove parallel for send as tests --- internal/provider/resource_gmail_send_as_alias_test.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/internal/provider/resource_gmail_send_as_alias_test.go b/internal/provider/resource_gmail_send_as_alias_test.go index 6dc97e00..cc685507 100644 --- a/internal/provider/resource_gmail_send_as_alias_test.go +++ b/internal/provider/resource_gmail_send_as_alias_test.go @@ -11,8 +11,6 @@ import ( ) func TestAccResourceGmailSendAsAlias_basic(t *testing.T) { - t.Parallel() - gmailUser := os.Getenv("GOOGLEWORKSPACE_TEST_GMAIL_USER") if gmailUser == "" { @@ -49,8 +47,6 @@ func TestAccResourceGmailSendAsAlias_basic(t *testing.T) { } func TestAccResourceGmailSendAsAlias_full(t *testing.T) { - t.Parallel() - gmailUser := os.Getenv("GOOGLEWORKSPACE_TEST_GMAIL_USER") if gmailUser == "" { @@ -95,8 +91,6 @@ func TestAccResourceGmailSendAsAlias_full(t *testing.T) { } func TestAccResourceGmailSendAsAlias_defaultCannotBeSetToFalseDirectly(t *testing.T) { - t.Parallel() - gmailUser := os.Getenv("GOOGLEWORKSPACE_TEST_GMAIL_USER") if gmailUser == "" { @@ -132,8 +126,6 @@ func TestAccResourceGmailSendAsAlias_defaultCannotBeSetToFalseDirectly(t *testin } func TestAccResourceGmailSendAsAlias_switchingDefaultCausesDiff(t *testing.T) { - t.Parallel() - gmailUser := os.Getenv("GOOGLEWORKSPACE_TEST_GMAIL_USER") if gmailUser == "" { From 5e63f218716aba57246d980ada6c0486acd9d816 Mon Sep 17 00:00:00 2001 From: Alex Pilon Date: Tue, 27 Jul 2021 11:25:48 -0400 Subject: [PATCH 8/9] PR comments --- .../provider/resource_gmail_send_as_alias.go | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/internal/provider/resource_gmail_send_as_alias.go b/internal/provider/resource_gmail_send_as_alias.go index 6ee20d0a..a6b7c647 100644 --- a/internal/provider/resource_gmail_send_as_alias.go +++ b/internal/provider/resource_gmail_send_as_alias.go @@ -26,15 +26,7 @@ func resourceGmailSendAsAlias() *schema.Resource { DeleteContext: resourceGmailSendAsAliasDelete, Importer: &schema.ResourceImporter{ - StateContext: func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - idParts := strings.Split(d.Id(), sendAsIdSeparator) - if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { - return nil, fmt.Errorf("Unexpected format of ID (%q), expected primary-email%ssend-as-email", d.Id(), sendAsIdSeparator) - } - d.Set("primary_email", idParts[0]) - d.Set("send_as_email", idParts[1]) - return []*schema.ResourceData{d}, nil - }, + StateContext: resourceGmailSendAsAliasImport, }, Schema: map[string]*schema.Schema{ @@ -111,7 +103,7 @@ func resourceGmailSendAsAlias() *schema.Resource { Sensitive: true, }, "security_mode": { - Description: "he protocol that will be used to secure communication with the SMTP service.", + Description: "The protocol that will be used to secure communication with the SMTP service.", Type: schema.TypeString, Optional: true, Default: "securityModeUnspecified", @@ -125,6 +117,13 @@ func resourceGmailSendAsAlias() *schema.Resource { Type: schema.TypeString, Computed: true, }, + // Adding a computed id simply to override the `optional` id that gets added in the SDK + // that will then display improperly in the docs + "id": { + Description: "The ID of this resource.", + Type: schema.TypeString, + Computed: true, + }, }, } } @@ -267,6 +266,16 @@ func resourceGmailSendAsAliasDelete(ctx context.Context, d *schema.ResourceData, return nil } +func resourceGmailSendAsAliasImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + idParts := strings.Split(d.Id(), sendAsIdSeparator) + if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { + return nil, fmt.Errorf("Unexpected format of ID (%q), expected primary-email%ssend-as-email", d.Id(), sendAsIdSeparator) + } + d.Set("primary_email", idParts[0]) + d.Set("send_as_email", idParts[1]) + return []*schema.ResourceData{d}, nil +} + func expandSmtpMsa(smtpMsa []interface{}) *gmail.SmtpMsa { if len(smtpMsa) == 0 { return nil From 71629ff9140cde2e7f5e643b882a93f7cbeae18b Mon Sep 17 00:00:00 2001 From: Alex Pilon Date: Tue, 27 Jul 2021 11:27:53 -0400 Subject: [PATCH 9/9] regen docs --- docs/resources/gmail_send_as_alias.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/resources/gmail_send_as_alias.md b/docs/resources/gmail_send_as_alias.md index 4e814dee..1d578b9b 100644 --- a/docs/resources/gmail_send_as_alias.md +++ b/docs/resources/gmail_send_as_alias.md @@ -45,7 +45,6 @@ resource "googleworkspace_gmail_send_as_alias" "test" { ### Optional - **display_name** (String) A name that appears in the 'From:' header for mail sent using this alias. For custom 'from' addresses, when this is empty, Gmail will populate the 'From:' header with the name that is used for the primary address associated with the account. If the admin has disabled the ability for users to update their name format, requests to update this field for the primary login will silently fail. -- **id** (String) The ID of this resource. - **is_default** (Boolean) Whether this address is selected as the default 'From:' address in situations such as composing a new message or sending a vacation auto-reply. Every Gmail account has exactly one default send-as address, so the only legal value that clients may write to this field is true. Changing this from false to true for an address will result in this field becoming false for the other previous default address. Toggling an existing alias' default to false is not possible, another alias must be added/imported and toggled to true to remove the default from an existing alias. To avoid drift with Terraform, please change the previous default's config to false AFTER a new default is applied and perform a refresh to synchronize with remote state. - **reply_to_address** (String) An optional email address that is included in a 'Reply-To:' header for mail sent using this alias. If this is empty, Gmail will not generate a 'Reply-To:' header. - **signature** (String) An optional HTML signature that is included in messages composed with this alias in the Gmail web UI. This signature is added to new emails only. @@ -54,6 +53,7 @@ resource "googleworkspace_gmail_send_as_alias" "test" { ### Read-Only +- **id** (String) The ID of this resource. - **is_primary** (Boolean) Whether this address is the primary address used to login to the account. Every Gmail account has exactly one primary address, and it cannot be deleted from the collection of send-as aliases. - **verification_status** (String) Indicates whether this address has been verified for use as a send-as alias. @@ -68,7 +68,7 @@ Required: Optional: - **password** (String, Sensitive) The password that will be used for authentication with the SMTP service. This is a write-only field that can be specified in requests to create or update SendAs settings; it is never populated in responses. -- **security_mode** (String) he protocol that will be used to secure communication with the SMTP service. Defaults to `securityModeUnspecified`. +- **security_mode** (String) The protocol that will be used to secure communication with the SMTP service. Defaults to `securityModeUnspecified`. - **username** (String) The username that will be used for authentication with the SMTP service. This is a write-only field that can be specified in requests to create or update SendAs settings; it is never populated in responses. ## Import