Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 69 additions & 8 deletions internal/services/mssql/mssql_server_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import (
"github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/serverconnectionpolicies"
"github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/servers"
"github.com/hashicorp/go-azure-sdk/sdk/client/pollers"
"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
schemaValidation "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/hashicorp/terraform-provider-azurerm/helpers/azure"
"github.com/hashicorp/terraform-provider-azurerm/helpers/tf"
"github.com/hashicorp/terraform-provider-azurerm/internal/clients"
Expand All @@ -34,7 +37,6 @@ import (
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation"
"github.com/hashicorp/terraform-provider-azurerm/internal/timeouts"
"github.com/hashicorp/terraform-provider-azurerm/utils"
)

func resourceMsSqlServer() *pluginsdk.Resource {
Expand All @@ -49,6 +51,10 @@ func resourceMsSqlServer() *pluginsdk.Resource {
return err
}),

ValidateRawResourceConfigFuncs: []schema.ValidateRawResourceConfigFunc{
schemaValidation.PreferWriteOnlyAttribute(cty.GetAttrPath("administrator_login_password"), cty.GetAttrPath("administrator_login_password_wo")),
},

Timeouts: &pluginsdk.ResourceTimeout{
Create: pluginsdk.DefaultTimeout(60 * time.Minute),
Read: pluginsdk.DefaultTimeout(5 * time.Minute),
Expand Down Expand Up @@ -84,15 +90,30 @@ func resourceMsSqlServer() *pluginsdk.Resource {
Computed: true,
ForceNew: true,
AtLeastOneOf: []string{"administrator_login", "azuread_administrator.0.azuread_authentication_only"},
RequiredWith: []string{"administrator_login", "administrator_login_password"},
},

"administrator_login_password": {
Type: pluginsdk.TypeString,
Type: pluginsdk.TypeString,
Optional: true,
Sensitive: true,
AtLeastOneOf: []string{"administrator_login_password", "administrator_login_password_wo", "azuread_administrator.0.azuread_authentication_only"},
ConflictsWith: []string{"administrator_login_password_wo"},
},

"administrator_login_password_wo": {
Type: pluginsdk.TypeString,
Optional: true,
Sensitive: true,
WriteOnly: true,
AtLeastOneOf: []string{"administrator_login_password_wo", "administrator_login_password", "azuread_administrator.0.azuread_authentication_only"},
ConflictsWith: []string{"administrator_login_password"},
RequiredWith: []string{"administrator_login_password_wo_version"},
},

"administrator_login_password_wo_version": {
Type: pluginsdk.TypeInt,
Optional: true,
Sensitive: true,
AtLeastOneOf: []string{"administrator_login_password", "azuread_administrator.0.azuread_authentication_only"},
RequiredWith: []string{"administrator_login", "administrator_login_password"},
RequiredWith: []string{"administrator_login_password_wo"},
},

"azuread_administrator": {
Expand Down Expand Up @@ -197,6 +218,8 @@ func resourceMsSqlServer() *pluginsdk.Resource {
pluginsdk.CustomizeDiffShim(msSqlMinimumTLSVersionDiff),

pluginsdk.CustomizeDiffShim(msSqlPasswordChangeWhenAADAuthOnly),

pluginsdk.CustomizeDiffShim(msSqlAdministratorLoginPassword),
),
}

Expand Down Expand Up @@ -240,6 +263,11 @@ func resourceMsSqlServerCreate(d *pluginsdk.ResourceData, meta interface{}) erro
return tf.ImportAsExistsError("azurerm_mssql_server", id.ID())
}

woAdminLoginPassword, err := pluginsdk.GetWriteOnly(d, "administrator_login_password_wo", cty.String)
if err != nil {
return err
}

props := servers.Server{
Location: location,
Tags: tags.Expand(d.Get("tags").(map[string]interface{})),
Expand All @@ -251,11 +279,15 @@ func resourceMsSqlServerCreate(d *pluginsdk.ResourceData, meta interface{}) erro
}

if v := d.Get("administrator_login"); v.(string) != "" {
props.Properties.AdministratorLogin = utils.String(v.(string))
props.Properties.AdministratorLogin = pointer.To(v.(string))
}

if v := d.Get("administrator_login_password"); v.(string) != "" {
props.Properties.AdministratorLoginPassword = utils.String(v.(string))
props.Properties.AdministratorLoginPassword = pointer.To(v.(string))
}

if !woAdminLoginPassword.IsNull() {
props.Properties.AdministratorLoginPassword = pointer.To(woAdminLoginPassword.AsString())
}

// NOTE: You must set the admin before setting the values of the admin...
Expand Down Expand Up @@ -389,6 +421,16 @@ func resourceMsSqlServerUpdate(d *pluginsdk.ResourceData, meta interface{}) erro
payload.Properties.AdministratorLoginPassword = pointer.To(adminPassword)
}

if d.HasChange("administrator_login_password_wo_version") {
woAdminLoginPassword, err := pluginsdk.GetWriteOnly(d, "administrator_login_password_wo", cty.String)
if err != nil {
return err
}
if !woAdminLoginPassword.IsNull() {
payload.Properties.AdministratorLoginPassword = pointer.To(woAdminLoginPassword.AsString())
}
}

if d.HasChange("minimum_tls_version") {
payload.Properties.MinimalTlsVersion = pointer.To(servers.MinimalTlsVersion(d.Get("minimum_tls_version").(string)))
}
Expand Down Expand Up @@ -524,6 +566,7 @@ func resourceMsSqlServerRead(d *pluginsdk.ResourceData, meta interface{}) error
if props := model.Properties; props != nil {
d.Set("version", props.Version)
d.Set("administrator_login", props.AdministratorLogin)
d.Set("administrator_login_password_wo_version", d.Get("administrator_login_password_wo_version").(int))
d.Set("fully_qualified_domain_name", props.FullyQualifiedDomainName)

// todo remove `|| *v == "None"` when https://github.com/Azure/azure-rest-api-specs/issues/24348 is addressed
Expand Down Expand Up @@ -721,3 +764,21 @@ func msSqlPasswordChangeWhenAADAuthOnly(ctx context.Context, d *pluginsdk.Resour
}
return
}

// msSqlAdministratorLoginPassword checks to make sure that one of `administrator_login_password_wo` or `administrator_login_password` is set when `administrator_login` is specified.
func msSqlAdministratorLoginPassword(ctx context.Context, d *pluginsdk.ResourceDiff, _ interface{}) (err error) {
adminLogin := d.GetRawConfig().AsValueMap()["administrator_login"]
if !adminLogin.IsNull() && adminLogin.AsString() != "" {
woAdminLoginPassword, err := pluginsdk.GetWriteOnlyFromDiff(d, "administrator_login_password_wo", cty.String)
if err != nil {
return err
}

password := d.GetRawConfig().AsValueMap()["administrator_login_password"]

if woAdminLoginPassword.IsNull() && password.IsNull() {
return fmt.Errorf("expected `administrator_login_password` or `administrator_login_password_wo` to be set when `administrator_login` is specified")
}
}
return
}
72 changes: 70 additions & 2 deletions internal/services/mssql/mssql_server_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"testing"

"github.com/hashicorp/go-azure-helpers/lang/pointer"
"github.com/hashicorp/go-azure-helpers/lang/response"
"github.com/hashicorp/go-azure-helpers/resourcemanager/commonids"
"github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/servers"
Expand All @@ -17,7 +18,6 @@ import (
"github.com/hashicorp/terraform-provider-azurerm/internal/features"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/mssql/parse"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
"github.com/hashicorp/terraform-provider-azurerm/utils"
)

type MsSqlServerResource struct{}
Expand Down Expand Up @@ -305,6 +305,47 @@ func TestAccMsSqlServer_CMKServerTagsUpdate(t *testing.T) {
})
}

func TestAccMsSqlServer_writeOnlyAdminLoginPassword(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_mssql_server", "test")
r := MsSqlServerResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.writeOnlyAdminLoginPassword(data, "7h1515K4711-secret", 1),
Check: check.That(data.ResourceName).ExistsInAzure(r),
},
data.ImportStep("administrator_login_password_wo_version"),
{
Config: r.writeOnlyAdminLoginPassword(data, "7h1515K4711-updated", 2),
Check: check.That(data.ResourceName).ExistsInAzure(r),
},
data.ImportStep("administrator_login_password_wo_version"),
})
}

func TestAccMsSqlServer_updateToWriteOnlyPassword(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_mssql_server", "test")
r := MsSqlServerResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.basic(data),
Check: check.That(data.ResourceName).ExistsInAzure(r),
},
data.ImportStep("administrator_login_password"),
{
Config: r.writeOnlyAdminLoginPassword(data, "7h1515K4711-secret", 1),
Check: check.That(data.ResourceName).ExistsInAzure(r),
},
data.ImportStep("administrator_login_password", "administrator_login_password_wo_version"),
{
Config: r.basic(data),
Check: check.That(data.ResourceName).ExistsInAzure(r),
},
data.ImportStep("administrator_login_password"),
})
}
Comment on lines +308 to +363
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests need to be skipped for Terraform versions < 1.11. There's an example in the preliminary guide I shared on how to do that, or you can take a look at the tests over in my PR.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh shoot! Swapped! Thank you thank you


func (MsSqlServerResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) {
id, err := parse.ServerID(state.ID)
if err != nil {
Expand All @@ -321,7 +362,7 @@ func (MsSqlServerResource) Exists(ctx context.Context, client *clients.Client, s
return nil, fmt.Errorf("reading SQL Server %q (Resource Group %q): %v", id.Name, id.ResourceGroup, err)
}

return utils.Bool(resp.Model != nil), nil
return pointer.To(resp.Model != nil), nil
}

func (MsSqlServerResource) basic(data acceptance.TestData) string {
Expand Down Expand Up @@ -1059,3 +1100,30 @@ resource "azurerm_key_vault_key" "test" {
}
`, data.RandomInteger, data.Locations.Primary, data.RandomString)
}

func (MsSqlServerResource) writeOnlyAdminLoginPassword(data acceptance.TestData, secret string, version int) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}

resource "azurerm_resource_group" "test" {
name = "acctestRG-mssql-%[1]d"
location = "%[2]s"
}

%s

resource "azurerm_mssql_server" "test" {
name = "acctestsqlserver%[1]d"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
version = "12.0"
administrator_login = "missadministrator"
administrator_login_password_wo_version = %[4]d
administrator_login_password_wo = ephemeral.azurerm_key_vault_secret.test.value

outbound_network_restriction_enabled = true
}
`, data.RandomInteger, data.Locations.Primary, acceptance.WriteOnlyKeyVaultSecretTemplate(data, secret), version)
}
13 changes: 13 additions & 0 deletions internal/tf/pluginsdk/write_only.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,16 @@ func GetWriteOnly(d *ResourceData, name string, attributeType cty.Type) (*cty.Va
}
return pointer.To(value), nil
}

// GetWriteOnlyFromDiff gets a write only attribute from the diff, checking that it is of an expected type and subsequently returns it
func GetWriteOnlyFromDiff(d *ResourceDiff, name string, attributeType cty.Type) (*cty.Value, error) {
value, diags := d.GetRawConfigAt(cty.GetAttrPath(name))
if diags.HasError() {
return nil, fmt.Errorf("retrieving write-only attribute `%s`: %+v", name, diags)
}

if !value.Type().Equals(attributeType) {
return nil, fmt.Errorf("retrieving write-only attribute `%s`: value is not of type %v", name, attributeType)
}
return pointer.To(value), nil
}
6 changes: 5 additions & 1 deletion website/docs/r/mssql_server.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,11 @@ The following arguments are supported:

* `administrator_login` - (Optional) The administrator login name for the new server. Required unless `azuread_authentication_only` in the `azuread_administrator` block is `true`. When omitted, Azure will generate a default username which cannot be subsequently changed. Changing this forces a new resource to be created.

* `administrator_login_password` - (Optional) The password associated with the `administrator_login` user. Needs to comply with Azure's [Password Policy](https://msdn.microsoft.com/library/ms161959.aspx). Required unless `azuread_authentication_only` in the `azuread_administrator` block is `true`.
* `administrator_login_password` - (Optional) The password associated with the `administrator_login` user. Needs to comply with Azure's [Password Policy](https://msdn.microsoft.com/library/ms161959.aspx). Either `administrator_login_password` or `administrator_login_password_wo` is required unless `azuread_authentication_only` in the `azuread_administrator` block is `true`.

* `administrator_login_password_wo` - (Optional, Write-Only) The Password associated with the `administrator_login` user. Needs to comply with Azure's [Password Policy](https://msdn.microsoft.com/library/ms161959.aspx). Either `administrator_login_password` or `administrator_login_password_wo` is required unless `azuread_authentication_only` in the `azuread_administrator` block is `true`.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Matt suggested adding a note instead of duplicating the either x or y is required information on the property descriptions on my PRs which I've updated. Can you make that change here as well so it's consistent?

Suggested change
* `administrator_login_password` - (Optional) The password associated with the `administrator_login` user. Needs to comply with Azure's [Password Policy](https://msdn.microsoft.com/library/ms161959.aspx). Either `administrator_login_password` or `administrator_login_password_wo` is required unless `azuread_authentication_only` in the `azuread_administrator` block is `true`.
* `administrator_login_password_wo` - (Optional, Write-Only) The Password associated with the `administrator_login` user. Needs to comply with Azure's [Password Policy](https://msdn.microsoft.com/library/ms161959.aspx). Either `administrator_login_password` or `administrator_login_password_wo` is required unless `azuread_authentication_only` in the `azuread_administrator` block is `true`.
* `administrator_login_password` - (Optional) The password associated with the `administrator_login` user. Needs to comply with Azure's [Password Policy](https://msdn.microsoft.com/library/ms161959.aspx).
* `administrator_login_password_wo` - (Optional, Write-Only) The Password associated with the `administrator_login` user. Needs to comply with Azure's [Password Policy](https://msdn.microsoft.com/library/ms161959.aspx).
~> **Note:** Either `administrator_login_password` or `administrator_login_password_wo` is required unless `azuread_authentication_only` in the `azuread_administrator` block is `true`.

*
* `administrator_login_password_wo_version` - (Optional) An integer value used to trigger an update for `administrator_login_password_wo`. This property should be incremented when updating `administrator_login_password_wo`.

* `azuread_administrator` - (Optional) An `azuread_administrator` block as defined below.

Expand Down