Skip to content

Commit

Permalink
expand organizationId validation
Browse files Browse the repository at this point in the history
  • Loading branch information
KenSpur committed Sep 7, 2023
1 parent 9416256 commit e7d138d
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 10 deletions.
16 changes: 9 additions & 7 deletions internal/services/iotcentral/iotcentral_organization_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,20 @@ func (r IotCentralOrganizationResource) Arguments() map[string]*pluginsdk.Schema
ForceNew: true,
},
"organization_id": {
Type: pluginsdk.TypeString,
Required: true,
ForceNew: true,
Type: pluginsdk.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validate.OrganizationID,
},
"display_name": {
Type: pluginsdk.TypeString,
Required: true,
},
"parent_organization_id": {
Type: pluginsdk.TypeString,
Optional: true,
ForceNew: true,
Type: pluginsdk.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validate.OrganizationID,
},
}
}
Expand All @@ -65,7 +67,7 @@ func (r IotCentralOrganizationResource) ModelObject() interface{} {
}

func (r IotCentralOrganizationResource) IDValidationFunc() pluginsdk.SchemaValidateFunc {
return validate.OrganizationID
return validate.ID
}

func (r IotCentralOrganizationResource) Create() sdk.ResourceFunc {
Expand Down
61 changes: 59 additions & 2 deletions internal/services/iotcentral/validate/organization.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ package validate
import (
"fmt"
"net/url"
"regexp"
"strings"

"github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation"
)

func OrganizationID(i interface{}, k string) (warnings []string, errors []error) {
func ID(i interface{}, k string) (warnings []string, errors []error) {
if warnings, errors = validation.StringIsNotEmpty(i, k); len(errors) > 0 {
return warnings, errors
}
Expand All @@ -21,7 +22,8 @@ func OrganizationID(i interface{}, k string) (warnings []string, errors []error)

idURL, err := url.ParseRequestURI(id)
if err != nil {
errors = append(errors, fmt.Errorf("cannot parse azure iot central organization ID: %s", err))
errors = append(errors, fmt.Errorf("cannot parse azure iot central organization ID as URI: %s", err))
return warnings, errors
}

path := idURL.Path
Expand All @@ -33,17 +35,72 @@ func OrganizationID(i interface{}, k string) (warnings []string, errors []error)

if len(components) != 3 {
errors = append(errors, fmt.Errorf("iot central organization should have 3 segments, found %d segment(s) in %q", len(components), id))
return warnings, errors
}

apiString := components[0]
if apiString != "api" {
errors = append(errors, fmt.Errorf("iot central organization should have api as first segment, found %q", apiString))
return warnings, errors
}

organizationsString := components[1]
if organizationsString != "organizations" {
errors = append(errors, fmt.Errorf("iot central organization should have organizations as second segment, found %q", organizationsString))
return warnings, errors
}

organizationIdString := components[2]
err = validateOrganizationId(organizationIdString)
if err != nil {
errors = append(errors, err)
return warnings, errors
}

return warnings, errors
}

func OrganizationID(i interface{}, k string) (warnings []string, errors []error) {
id, ok := i.(string)
if !ok {
errors = append(errors, fmt.Errorf("expected %s to be a string", k))
return warnings, errors
}

err := validateOrganizationId(id)
if err != nil {
errors = append(errors, err)
return warnings, errors
}

return warnings, errors
}

func validateOrganizationId(id string) error {
// Ensure the string follows the desired format.
// Regex pattern: ^(?!-)[a-z0-9-]{1,48}[a-z0-9]$
// The negative lookahead (?!-) is not supported in Go's standard regexp package
formatPattern := `^[a-z0-9-]{1,48}[a-z0-9]$`
formatRegex, err := regexp.Compile(formatPattern)
if err != nil {
return fmt.Errorf("error compiling format regex: %s error: %+v", formatPattern, err)
}

if !formatRegex.MatchString(id) {
return fmt.Errorf("iot central organizationId %q is invalid", id)
}

// Ensure the string does not start with a hyphen.
// Solves for (?!-)
startHyphenPattern := `^-`
startHyphenRegex, err := regexp.Compile(startHyphenPattern)
if err != nil {
return fmt.Errorf("error compiling start hyphen regex: %s error: %+v", startHyphenPattern, err)
}

if startHyphenRegex.MatchString(id) {
return fmt.Errorf("iot central organizationId %q is invalid", id)
}

return nil
}
55 changes: 54 additions & 1 deletion internal/services/iotcentral/validate/organization_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"testing"
)

func TestNestedItemId(t *testing.T) {
func TestID(t *testing.T) {
cases := []struct {
Input string
ExpectError bool
Expand Down Expand Up @@ -39,6 +39,59 @@ func TestNestedItemId(t *testing.T) {
},
}

for _, tc := range cases {
warnings, err := ID(tc.Input, "example")
if err != nil {
if !tc.ExpectError {
t.Fatalf("Got error for input %q: %+v", tc.Input, err)
}

return
}

if tc.ExpectError && len(warnings) == 0 {
t.Fatalf("Got no errors for input %q but expected some", tc.Input)
} else if !tc.ExpectError && len(warnings) > 0 {
t.Fatalf("Got %d errors for input %q when didn't expect any", len(warnings), tc.Input)
}
}
}

func TestOrganizationID(t *testing.T) {
cases := []struct {
Input string
ExpectError bool
}{
{
Input: "-invalid-start",
ExpectError: true,
},
{
Input: "invalid--hyphen",
ExpectError: true,
},
{
Input: "1234567890123456789012345678901234567890123456789",
ExpectError: true,
},
{
Input: "valid-string1",
ExpectError: false,
},
{
Input: "validstring2",
ExpectError: false,
},
{
Input: "v",
ExpectError: false,
},
{
Input: "1",
ExpectError: true,
},
}

for _, tc := range cases {
warnings, err := OrganizationID(tc.Input, "example")
if err != nil {
Expand Down

0 comments on commit e7d138d

Please sign in to comment.