Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix azurerm_databricks_workspace.name validation #8997

Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -355,23 +355,29 @@ func ValidateDatabricksWorkspaceName(i interface{}, k string) (warnings []string
return warnings, errors
}

// Cannot be empty
// The Azure Portal shows the following validation criteria:

// 1) Cannot be empty
if len(v) == 0 {
errors = append(errors, fmt.Errorf("%q cannot be an empty string: %q", k, v))
// Treating this as a special case and returning early to match Azure Portal behavior.
return warnings, errors
}

// First, second, and last characters must be a letter or number with a total length between 3 to 64 characters
// 2) Must be at least 3 characters:
if len(v) < 3 {
errors = append(errors, fmt.Errorf("%q must be at least 3 characters: %q", k, v))
}

// 3) The value must have a length of at most 30.
// NOTE: Restricted name to 30 characters because that is the restriction in Azure Portal even though the API supports 64 characters
if !regexp.MustCompile("^[a-zA-Z0-9]{2}[-_a-zA-Z0-9]{0,27}[a-zA-Z0-9]{1}$").MatchString(v) {
errors = append(errors, fmt.Errorf("%q must be 3 - 30 characters in length", k))
errors = append(errors, fmt.Errorf("%q first, second, and last characters must be a letter or number", k))
errors = append(errors, fmt.Errorf("%q can only contain letters, numbers, underscores, and hyphens", k))
if len(v) > 30 {
errors = append(errors, fmt.Errorf("%q must be no more than 30 characters: %q", k, v))
}

// No consecutive hyphens
if regexp.MustCompile("(--)").MatchString(v) {
errors = append(errors, fmt.Errorf("%q must not contain any consecutive hyphens", k))
// 4) Only alphanumeric characters, underscores, and hyphens are allowed.
if !regexp.MustCompile("^[a-zA-Z0-9_-]*$").MatchString(v) {
errors = append(errors, fmt.Errorf("%q can contain only alphanumeric characters, underscores, and hyphens: %q", k, v))
}

return warnings, errors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"net/http"
"regexp"
"strings"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
Expand All @@ -15,71 +16,89 @@ import (
)

func TestAzureRMDatabrickWorkspaceName(t *testing.T) {
const errEmpty = "cannot be an empty string"
const errMinLen = "must be at least 3 characters"
const errMaxLen = "must be no more than 30 characters"
const errAllowList = "can contain only alphanumeric characters, underscores, and hyphens"

cases := []struct {
Value string
ShouldError bool
Name string
Input string
ExpectedErrors []string
}{
// Happy paths:
{
Value: "hello",
ShouldError: false,
},
{
Value: "hello123there",
ShouldError: false,
},
{
Value: "hello-1-2-3-there",
ShouldError: false,
},
{
Value: "hello_1_2_3_there",
ShouldError: false,
Name: "Entire character allow-list",
Input: "aZ09_-",
},
{
Value: "hello-1-2-3-",
ShouldError: true,
Name: "Minimum character length",
Input: "---",
},
{
Value: "-hello-1-2-3",
ShouldError: true,
Name: "Maximum character length",
Input: "012345678901234567890123456789", // 30 chars
},

// Simple negative cases:
{
Value: "hello_1_2_3_",
ShouldError: true,
Name: "Introduce a non-allowed character",
Input: "aZ09_-$", // dollar sign
ExpectedErrors: []string{errAllowList},
},
{
Value: "_hello_1_2_3",
ShouldError: true,
Name: "Below minimum character length",
Input: "--",
ExpectedErrors: []string{errMinLen},
},
{
Value: "hello!there",
ShouldError: true,
Name: "Above maximum character length",
Input: "0123456789012345678901234567890", // 31 chars
ExpectedErrors: []string{errMaxLen},
},
{
Value: "hello--there",
ShouldError: true,
Name: "Specifically test for emptiness",
Input: "",
ExpectedErrors: []string{errEmpty},
},

// Complex negative cases
{
Value: "!hellothere",
ShouldError: true,
Name: "Too short and non-allowed char",
Input: "*^",
ExpectedErrors: []string{errMinLen, errAllowList},
},
{
Value: "hellothere!",
ShouldError: true,
Name: "Too long and non-allowed char",
Input: "012345678901234567890123456789ß",
ExpectedErrors: []string{errMaxLen, errAllowList},
},
}

for _, tc := range cases {
_, errors := databricks.ValidateDatabricksWorkspaceName(tc.Value, "test")

hasErrors := len(errors) > 0
if hasErrors && !tc.ShouldError {
t.Fatalf("Expected no errors but got %d for %q", len(errors), tc.Value)
errsContain := func(errors []error, text string) bool {
for _, err := range errors {
if strings.Contains(err.Error(), text) {
return true
}
}
return false
}

if !hasErrors && tc.ShouldError {
t.Fatalf("Expected no errors but got %d for %q", len(errors), tc.Value)
}
t.Parallel()
for _, tc := range cases {
t.Run(tc.Name, func(t *testing.T) {
_, errors := databricks.ValidateDatabricksWorkspaceName(tc.Input, "azurerm_databricks_workspace.test.name")

if len(errors) != len(tc.ExpectedErrors) {
t.Fatalf("Expected %d errors but got %d for %q: %v", len(tc.ExpectedErrors), len(errors), tc.Input, errors)
}

for _, expectedError := range tc.ExpectedErrors {
if !errsContain(errors, expectedError) {
t.Fatalf("Errors did not contain expected error: %s", expectedError)
}
}
})
}
}

Expand Down