Repository: mrparkers/terraform-provider-keycloak
Pull request #122 (2019-06-12 10:52:43)

adding possibility to add SMTP configuration
Adding possibility to add SMTP Server configuration.

tomrutsaert: All request changes were added + I have updated the branch to latest version of master

Patch:
diff --git a/example/main.tf b/example/main.tf
index eb6844c..ae0d8e7 100644
--- a/example/main.tf
+++ b/example/main.tf
@@ -9,6 +9,21 @@ resource "keycloak_realm" "test" {
   enabled      = true
   display_name = "foo"
 
+  smtp_server {
+	  host = "mysmtphost.com"
+	  port = 25
+	  from_display_name = "Tom"
+	  from = "tom@myhost.com"
+	  reply_to_display_name = "Tom"
+	  reply_to = "tom@myhost.com"
+	  auth = true
+	  user = "tom"
+	  password = "tom"
+	  ssl = true
+	  starttls = true
+	  envelope_from= "nottom@myhost.com"
+  }
+
   account_theme = "base"
 
   access_code_lifespan = "30m"
diff --git a/keycloak/realm.go b/keycloak/realm.go
index d45a2f6..b817ab7 100644
--- a/keycloak/realm.go
+++ b/keycloak/realm.go
@@ -20,6 +20,9 @@ type Realm struct {
 	LoginWithEmailAllowed       bool `json:"loginWithEmailAllowed"`
 	DuplicateEmailsAllowed      bool `json:"duplicateEmailsAllowed"`
 
+	//SMTP Server
+	SmtpServer SmtpServer `json:"smtpServer"`
+
 	// Themes
 	LoginTheme   string `json:"loginTheme,omitempty"`
 	AccountTheme string `json:"accountTheme,omitempty"`
@@ -42,6 +45,21 @@ type Realm struct {
 	ActionTokenGeneratedByAdminLifespan int  `json:"actionTokenGeneratedByAdminLifespan,omitempty"`
 }
 
+type SmtpServer struct {
+	StartTls           string `json:"starttls,omitempty"`
+	Auth               string `json:"auth,omitempty"`
+	Port               string `json:"port,omitempty"`
+	Host               string `json:"host,omitempty"`
+	ReplyTo            string `json:"replyTo,omitempty"`
+	ReplyToDisplayName string `json:"replyToDisplayName,omitempty"`
+	From               string `json:"from,omitempty"`
+	FromDisplayName    string `json:"fromDisplayName,omitempty"`
+	EnvelopeFrom       string `json:"envelopeFrom,omitempty"`
+	Ssl                string `json:"ssl,omitempty"`
+	User               string `json:"user,omitempty"`
+	Password           string `json:"password,omitempty"`
+}
+
 func (keycloakClient *KeycloakClient) NewRealm(realm *Realm) error {
 	_, _, err := keycloakClient.post("/realms", realm)
 
diff --git a/provider/resource_keycloak_realm.go b/provider/resource_keycloak_realm.go
index a1dcb08..2324690 100644
--- a/provider/resource_keycloak_realm.go
+++ b/provider/resource_keycloak_realm.go
@@ -73,6 +73,70 @@ func resourceKeycloakRealm() *schema.Resource {
 				Computed: true,
 			},
 
+			//Smtp server
+
+			"smtp_server": {
+				Type:     schema.TypeSet,
+				Optional: true,
+				MaxItems: 1,
+				Elem: &schema.Resource{
+					Schema: map[string]*schema.Schema{
+						"starttls": {
+							Type:     schema.TypeString,
+							Optional: true,
+						},
+						"auth": {
+							Type:     schema.TypeString,
+							Optional: true,
+						},
+						"port": {
+							Type:     schema.TypeString,
+							Optional: true,
+						},
+						"host": {
+							Type:     schema.TypeString,
+							Required: true,
+						},
+						"reply_to": {
+							Type:     schema.TypeString,
+							Optional: true,
+						},
+						"reply_to_display_name": {
+							Type:     schema.TypeString,
+							Optional: true,
+						},
+						"from": {
+							Type:     schema.TypeString,
+							Required: true,
+						},
+						"from_display_name": {
+							Type:     schema.TypeString,
+							Optional: true,
+						},
+						"envelope_from": {
+							Type:     schema.TypeString,
+							Optional: true,
+						},
+						"ssl": {
+							Type:     schema.TypeString,
+							Optional: true,
+						},
+						"user": {
+							Type:     schema.TypeString,
+							Optional: true,
+						},
+						"password": {
+							Type:      schema.TypeString,
+							Optional:  true,
+							Sensitive: true,
+							DiffSuppressFunc: func(_, smtpServerPassword, _ string, _ *schema.ResourceData) bool {
+								return smtpServerPassword == "**********"
+							},
+						},
+					},
+				},
+			},
+
 			// Themes
 
 			"login_theme": {
@@ -187,6 +251,28 @@ func getRealmFromData(data *schema.ResourceData) (*keycloak.Realm, error) {
 		DuplicateEmailsAllowed:      data.Get("duplicate_emails_allowed").(bool),
 	}
 
+	//smtp
+	if v, ok := data.GetOk("smtp_server"); ok {
+		smtpSettings := v.(*schema.Set).List()[0].(map[string]interface{})
+
+		smtpServer := keycloak.SmtpServer{
+			StartTls:           smtpSettings["starttls"].(string),
+			Auth:               smtpSettings["auth"].(string),
+			Port:               smtpSettings["port"].(string),
+			Host:               smtpSettings["host"].(string),
+			ReplyTo:            smtpSettings["reply_to"].(string),
+			ReplyToDisplayName: smtpSettings["reply_to_display_name"].(string),
+			From:               smtpSettings["from"].(string),
+			FromDisplayName:    smtpSettings["from_display_name"].(string),
+			EnvelopeFrom:       smtpSettings["envelope_from"].(string),
+			Ssl:                smtpSettings["ssl"].(string),
+			User:               smtpSettings["user"].(string),
+			Password:           smtpSettings["password"].(string),
+		}
+
+		realm.SmtpServer = smtpServer
+	}
+
 	// Themes
 
 	if loginTheme, ok := data.GetOk("login_theme"); ok {
@@ -322,6 +408,28 @@ func setRealmData(data *schema.ResourceData, realm *keycloak.Realm) {
 	data.Set("login_with_email_allowed", realm.LoginWithEmailAllowed)
 	data.Set("duplicate_emails_allowed", realm.DuplicateEmailsAllowed)
 
+	// Smtp Config
+
+	if (keycloak.SmtpServer{}) == realm.SmtpServer {
+		data.Set("smtp_server", nil)
+	} else {
+		smtpSettings := make(map[string]interface{})
+
+		smtpSettings["starttls"] = realm.SmtpServer.StartTls
+		smtpSettings["auth"] = realm.SmtpServer.Auth
+		smtpSettings["port"] = realm.SmtpServer.Port
+		smtpSettings["host"] = realm.SmtpServer.Host
+		smtpSettings["reply_to"] = realm.SmtpServer.ReplyTo
+		smtpSettings["reply_to_display_name"] = realm.SmtpServer.ReplyToDisplayName
+		smtpSettings["from"] = realm.SmtpServer.From
+		smtpSettings["from_display_name"] = realm.SmtpServer.FromDisplayName
+		smtpSettings["envelope_from"] = realm.SmtpServer.EnvelopeFrom
+		smtpSettings["ssl"] = realm.SmtpServer.Ssl
+		smtpSettings["user"] = realm.SmtpServer.User
+		smtpSettings["password"] = realm.SmtpServer.Password
+		data.Set("smtp_server", schema.NewSet(func(i interface{}) int { return 1 }, []interface{}{smtpSettings}))
+	}
+
 	// Themes
 	data.Set("login_theme", realm.LoginTheme)
 	data.Set("account_theme", realm.AccountTheme)
@@ -375,6 +483,11 @@ func resourceKeycloakRealmRead(data *schema.ResourceData, meta interface{}) erro
 		return handleNotFoundError(err, data)
 	}
 
+	if v, ok := data.GetOk("smtp_server"); ok {
+		// we can't trust the API to set this field correctly since it just responds with "**********" this implies a 'password only' change will not detected
+		smtpSettings := v.(*schema.Set).List()[0].(map[string]interface{})
+		realm.SmtpServer.Password = smtpSettings["password"].(string)
+	}
 	setRealmData(data, realm)
 
 	return nil


Test patch:
diff --git a/provider/resource_keycloak_realm_test.go b/provider/resource_keycloak_realm_test.go
index cd54fa5..aa1fc14 100644
--- a/provider/resource_keycloak_realm_test.go
+++ b/provider/resource_keycloak_realm_test.go
@@ -86,6 +86,66 @@ func TestAccKeycloakRealm_import(t *testing.T) {
 	})
 }
 
+func TestAccKeycloakRealm_SmtpServer(t *testing.T) {
+	realm := "terraform-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakRealmDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakRealm_WithSmtpServer(realm, "myhost.com", "My Host", "user"),
+				Check:  testAccCheckKeycloakRealmSmtp("keycloak_realm.realm", "myhost.com", "My Host", "user"),
+			},
+			{
+				Config: testKeycloakRealm_basic(realm, realm),
+				Check:  testAccCheckKeycloakRealmSmtp("keycloak_realm.realm", "", "", ""),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakRealm_SmtpServerUpdate(t *testing.T) {
+	realm := "terraform-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakRealmDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakRealm_WithSmtpServer(realm, "myhost.com", "My Host", "user"),
+				Check:  testAccCheckKeycloakRealmSmtp("keycloak_realm.realm", "myhost.com", "My Host", "user"),
+			},
+			{
+				Config: testKeycloakRealm_WithSmtpServer(realm, "myhost2.com", "My Host2", "user2"),
+				Check:  testAccCheckKeycloakRealmSmtp("keycloak_realm.realm", "myhost2.com", "My Host2", "user2"),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakRealm_SmtpServerInValid(t *testing.T) {
+	realm := "terraform-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakRealmDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config:      testKeycloakRealm_WithSmtpServerWithoutHost(realm, "My Host"),
+				ExpectError: regexp.MustCompile("config is invalid: Missing required argument: The argument \"host\" is required, but no definition was found."),
+			},
+			{
+				Config:      testKeycloakRealm_WithSmtpServerWithoutFrom(realm, "myhost.com"),
+				ExpectError: regexp.MustCompile("config is invalid: Missing required argument: The argument \"from\" is required, but no definition was found."),
+			},
+		},
+	})
+}
+
 func TestAccKeycloakRealm_themes(t *testing.T) {
 	realmOne := &keycloak.Realm{
 		Realm:        "terraform-" + acctest.RandString(10),
@@ -381,6 +441,29 @@ func testAccCheckKeycloakRealmDisplayName(resourceName string, displayName strin
 	}
 }
 
+func testAccCheckKeycloakRealmSmtp(resourceName, host, from, user string) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		realm, err := getRealmFromState(s, resourceName)
+		if err != nil {
+			return err
+		}
+
+		if realm.SmtpServer.Host != host {
+			return fmt.Errorf("expected realm %s to have smtp host set to %s, but was %s", realm.Realm, host, realm.SmtpServer.Host)
+		}
+
+		if realm.SmtpServer.From != from {
+			return fmt.Errorf("expected realm %s to have smtp from set to %s, but was %s", realm.Realm, from, realm.SmtpServer.From)
+		}
+
+		if realm.SmtpServer.User != user {
+			return fmt.Errorf("expected realm %s to have smtp user set to %s, but was %s", realm.Realm, user, realm.SmtpServer.User)
+		}
+
+		return nil
+	}
+}
+
 func testAccCheckKeycloakRealmDestroy() resource.TestCheckFunc {
 	return func(s *terraform.State) error {
 		for _, rs := range s.RootModule().Resources {
@@ -429,6 +512,76 @@ resource "keycloak_realm" "realm" {
 	`, realm, realmDisplayName)
 }
 
+func testKeycloakRealm_WithSmtpServer(realm, host, from, user string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+  realm = "%s"
+  enabled = true
+  display_name = "%s"
+  smtp_server {
+    host = "%s"
+    port = 25
+    from_display_name = "Tom"
+    from = "%s"
+    reply_to_display_name = "Tom"
+    reply_to = "tom@myhost.com"
+    auth = true
+    user = "%s"
+    password = "tom"
+    ssl = true
+    starttls = true
+    envelope_from = "nottom@myhost.com"
+  }
+}
+	`, realm, realm, host, from, user)
+}
+
+func testKeycloakRealm_WithSmtpServerWithoutHost(realm, from string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+  realm = "%s"
+  enabled = true
+  display_name = "%s"
+  smtp_server {
+    port = 25
+    from_display_name = "Tom"
+    from = "%s"
+    reply_to_display_name = "Tom"
+    reply_to = "tom@myhost.com"
+    auth = true
+    user = "tom"
+    password = "tom"
+    ssl = true
+    starttls = true
+    envelope_from = "nottom@myhost.com"
+  }
+}
+	`, realm, realm, from)
+}
+
+func testKeycloakRealm_WithSmtpServerWithoutFrom(realm, host string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+  realm = "%s"
+  enabled = true
+  display_name = "%s"
+  smtp_server {
+    host = "%s"
+    port = 25
+    from_display_name = "Tom"
+    reply_to_display_name = "Tom"
+    reply_to = "tom@myhost.com"
+    auth = true
+    user = "tom"
+    password = "tom"
+    ssl = true
+    starttls = true
+    envelope_from = "nottom@myhost.com"
+  }
+}
+	`, realm, realm, host)
+}
+
 func testKeycloakRealm_themes(realm *keycloak.Realm) string {
 	return fmt.Sprintf(`
 resource "keycloak_realm" "realm" {