diff --git a/docs/resources/gmail_send_as_alias.md b/docs/resources/gmail_send_as_alias.md new file mode 100644 index 00000000..1d578b9b --- /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. +- **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 + +- **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. + + +### 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) 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 + +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/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..2b228fed 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -14,6 +14,8 @@ import ( ) var DefaultClientScopes = []string{ + "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", "https://www.googleapis.com/auth/admin.directory.customer", @@ -101,17 +103,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..06b9826a 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,42 @@ func (c *apiClient) NewDirectoryService() (*directory.Service, diag.Diagnostics) return directoryService, diags } +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") + + // 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) + } + + 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..a6b7c647 --- /dev/null +++ b/internal/provider/resource_gmail_send_as_alias.go @@ -0,0 +1,306 @@ +package googleworkspace + +import ( + "context" + "fmt" + "log" + "strings" + + "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: "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, + UpdateContext: resourceGmailSendAsAliasUpdate, + DeleteContext: resourceGmailSendAsAliasDelete, + + Importer: &schema.ResourceImporter{ + StateContext: resourceGmailSendAsAliasImport, + }, + + 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. 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, + }, + "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: "The 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, + }, + // 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, + }, + }, + } +} + +func resourceGmailSendAsAliasCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*apiClient) + + primaryEmail := d.Get("primary_email").(string) + gmailService, diags := client.NewGmailService(ctx, primaryEmail) + if diags.HasError() { + return diags + } + + sendAsAliasService, diags := GetGmailSendAsAliasService(gmailService) + if diags.HasError() { + return diags + } + + sendAsEmail := d.Get("send_as_email").(string) + log.Printf("[DEBUG] Creating Gmail Send As Alias %q", primaryEmail+sendAsIdSeparator+sendAsEmail) + + sendAs, err := sendAsAliasService.Create("me", &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 { + return 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) + + primaryEmail := d.Get("primary_email").(string) + gmailService, diags := client.NewGmailService(ctx, primaryEmail) + 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()) + + _, 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), + 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) +} + +func resourceGmailSendAsAliasRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*apiClient) + + primaryEmail := d.Get("primary_email").(string) + gmailService, diags := client.NewGmailService(ctx, primaryEmail) + 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()) + + sendAs, err := sendAsAliasService.Get("me", d.Get("send_as_email").(string)).Do() + if err != nil { + return handleNotFoundError(err, d, d.Id()) + } + + 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) + + primaryEmail := d.Get("primary_email").(string) + gmailService, diags := client.NewGmailService(ctx, primaryEmail) + 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()) + + err := sendAsAliasService.Delete("me", 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 +} + +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 + } + 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/resource_gmail_send_as_alias_test.go b/internal/provider/resource_gmail_send_as_alias_test.go new file mode 100644 index 00000000..cc685507 --- /dev/null +++ b/internal/provider/resource_gmail_send_as_alias_test.go @@ -0,0 +1,315 @@ +package googleworkspace + +import ( + "fmt" + "os" + "regexp" + "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) { + 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, + }, + }, + }) +} + +func TestAccResourceGmailSendAsAlias_full(t *testing.T) { + 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) { + 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) { + 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" { + 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 +} +`, 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) +} 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