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

feat: SAML2 integration v1 readiness #2868

Merged
merged 20 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,8 @@ The Snowflake provider will use the following order of precedence when determini
## Currently deprecated resources

- [snowflake_database_old](./docs/resources/database_old)
- [snowflake_unsafe_execute](./docs/resources/unsafe_execute)
- [snowflake_saml_integration](./docs/resources/saml_integration) - use [snowflake_saml2_integration](./docs/resources/saml2_integration) instead
- [snowflake_unsafe_execute](./docs/resources/unsafe_execute)

## Currently deprecated datasources

Expand Down
276 changes: 261 additions & 15 deletions docs/resources/saml2_integration.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/resources/scim_integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,5 +137,5 @@ Read-Only:
Import is supported using the following syntax:

```shell
terraform import snowflake_scim_integration.example name
terraform import snowflake_scim_integration.example "name"
```
2 changes: 1 addition & 1 deletion examples/additional/deprecated_resources.MD
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
## Currently deprecated resources

- [snowflake_database_old](./docs/resources/database_old)
- [snowflake_unsafe_execute](./docs/resources/unsafe_execute)
- [snowflake_saml_integration](./docs/resources/saml_integration) - use [snowflake_saml2_integration](./docs/resources/saml2_integration) instead
- [snowflake_unsafe_execute](./docs/resources/unsafe_execute)
53 changes: 53 additions & 0 deletions pkg/acceptance/importchecks/import_checks.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package importchecks

import (
"errors"
"fmt"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
Expand All @@ -19,6 +20,22 @@ func ComposeImportStateCheck(fs ...resource.ImportStateCheckFunc) resource.Impor
}
}

// ComposeAggregateImportStateCheck does the same as ComposeImportStateCheck, but it aggregates all the occurred errors,
// instead of returning the first encountered one.
func ComposeAggregateImportStateCheck(fs ...resource.ImportStateCheckFunc) resource.ImportStateCheckFunc {
return func(s []*terraform.InstanceState) error {
var result []error

for i, f := range fs {
if err := f(s); err != nil {
result = append(result, fmt.Errorf("check %d/%d error: %w", i+1, len(fs), err))
}
}

return errors.Join(result...)
}
}

// TestCheckResourceAttrInstanceState is based on unexported testCheckResourceAttrInstanceState from teststep_providers_test.go
func TestCheckResourceAttrInstanceState(id string, attributeName, attributeValue string) resource.ImportStateCheckFunc {
return func(is []*terraform.InstanceState) error {
Expand All @@ -39,3 +56,39 @@ func TestCheckResourceAttrInstanceState(id string, attributeName, attributeValue
return fmt.Errorf("attribute %s not found in instance state", attributeName)
}
}

// TestCheckResourceAttrNotInInstanceState is based on unexported testCheckResourceAttrInstanceState from teststep_providers_test.go,
// but instead of comparing values, it only checks if the attribute is present in the InstanceState.
func TestCheckResourceAttrNotInInstanceState(id string, attributeName string) resource.ImportStateCheckFunc {
return func(is []*terraform.InstanceState) error {
for _, v := range is {
if v.ID != id {
continue
}

if _, ok := v.Attributes[attributeName]; ok {
return fmt.Errorf("attribute %s found in instance state, but expected not to be there", attributeName)
}
}

return nil
}
}

// TestCheckResourceAttrInstanceStateSet is based on unexported testCheckResourceAttrInstanceState from teststep_providers_test.go,
// but instead of comparing values, it only checks if the value is set.
func TestCheckResourceAttrInstanceStateSet(id string, attributeName string) resource.ImportStateCheckFunc {
return func(is []*terraform.InstanceState) error {
for _, v := range is {
if v.ID != id {
continue
}

if _, ok := v.Attributes[attributeName]; ok {
return nil
}
}

return fmt.Errorf("attribute %s not found in instance state", attributeName)
}
}
8 changes: 8 additions & 0 deletions pkg/resources/custom_diffs.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ func ForceNewIfChangeToEmptySlice[T any](key string) schema.CustomizeDiffFunc {
})
}

// ForceNewIfChangeToEmptySet sets a ForceNew for a list field which was set to an empty value.
func ForceNewIfChangeToEmptySet(key string) schema.CustomizeDiffFunc {
return customdiff.ForceNewIfChange(key, func(ctx context.Context, oldValue, newValue, meta any) bool {
oldList, newList := oldValue.(*schema.Set).List(), newValue.(*schema.Set).List()
return len(oldList) > 0 && len(newList) == 0
})
}

// ForceNewIfChangeToEmptyString sets a ForceNew for a string field which was set to an empty value.
func ForceNewIfChangeToEmptyString(key string) schema.CustomizeDiffFunc {
return customdiff.ForceNewIfChange(key, func(ctx context.Context, oldValue, newValue, meta any) bool {
Expand Down
63 changes: 63 additions & 0 deletions pkg/resources/custom_diffs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,3 +278,66 @@ func TestForceNewIfChangeToEmptySlice(t *testing.T) {
})
}
}

func TestForceNewIfChangeToEmptySet(t *testing.T) {
tests := []struct {
name string
stateValue map[string]string
rawConfigValue map[string]any
wantForceNew bool
}{
{
name: "empty to non-empty",
stateValue: map[string]string{},
rawConfigValue: map[string]any{
"value": []any{"foo"},
},
wantForceNew: false,
}, {
name: "empty to empty",
stateValue: map[string]string{},
rawConfigValue: map[string]any{},
wantForceNew: false,
}, {
name: "non-empty to empty",
stateValue: map[string]string{
"value.#": "1",
"value.0": "foo",
},
rawConfigValue: map[string]any{},
wantForceNew: true,
}, {
name: "non-empty to non-empty",
stateValue: map[string]string{
"value.#": "2",
"value.0": "foo",
"value.1": "bar",
},
rawConfigValue: map[string]any{
"value": []any{"foo"},
},
wantForceNew: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
diff := calculateDiffFromAttributes(t,
createProviderWithValuePropertyAndCustomDiff(t,
&schema.Schema{
Type: schema.TypeList,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Optional: true,
},
resources.ForceNewIfChangeToEmptySet(
"value",
),
),
tt.stateValue,
tt.rawConfigValue,
)
assert.Equal(t, tt.wantForceNew, diff.RequiresNew())
})
}
}
66 changes: 18 additions & 48 deletions pkg/resources/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,55 +141,25 @@ func GetPropertyAsPointer[T any](d *schema.ResourceData, property string) *T {
return &typedValue
}

func GetPropertyOfFirstNestedObjectByValueKey[T any](d *schema.ResourceData, propertyKey string) (*T, error) {
return GetPropertyOfFirstNestedObjectByKey[T](d, propertyKey, "value")
}

// GetPropertyOfFirstNestedObjectByKey should be used for single objects defined in the Terraform schema as
// schema.TypeList with MaxItems set to one and inner schema with single value. To easily retrieve
// the inner value, you can specify the top-level property with propertyKey and the nested value with nestedValueKey.
func GetPropertyOfFirstNestedObjectByKey[T any](d *schema.ResourceData, propertyKey string, nestedValueKey string) (*T, error) {
value, ok := d.GetOk(propertyKey)
if !ok {
return nil, fmt.Errorf("nested property %s not found", propertyKey)
}

typedValue, ok := value.([]any)
if !ok || len(typedValue) != 1 {
return nil, fmt.Errorf("nested property %s is not an array or has incorrect number of values: %d, expected: 1", propertyKey, len(typedValue))
}

typedNestedMap, ok := typedValue[0].(map[string]any)
if !ok {
return nil, fmt.Errorf("nested property %s is not of type map[string]any, got: %T", propertyKey, typedValue[0])
}

_, ok = typedNestedMap[nestedValueKey]
if !ok {
return nil, fmt.Errorf("nested value key %s couldn't be found in the nested property map %s", nestedValueKey, propertyKey)
}

typedNestedValue, ok := typedNestedMap[nestedValueKey].(T)
if !ok {
return nil, fmt.Errorf("nested property %s.%s is not of type %T, got: %T", propertyKey, nestedValueKey, *new(T), typedNestedMap[nestedValueKey])
// ParseCommaSeparatedStringArray can be used to parse Snowflake output containing a list in the format of "[item1, item2, ...]",
// the assumptions are that:
// 1. The list is enclosed by [] brackets, and they shouldn't be a part of any item's value
// 2. Items are separated by commas, and they shouldn't be a part of any item's value
// 3. items can have as many spaces in between, but after separation they will be trimmed and shouldn't be a part of any item's value
func ParseCommaSeparatedStringArray(value string) []string {
sfc-gh-asawicki marked this conversation as resolved.
Show resolved Hide resolved
if strings.HasPrefix(value, "[") && strings.HasSuffix(value, "]") {
if value == "[]" {
return make([]string, 0)
}
list := strings.Trim(value, "[]")
listItems := strings.Split(list, ",")
trimmedListItems := make([]string, len(listItems))
for i, item := range listItems {
trimmedListItems[i] = strings.TrimSpace(item)
}
return trimmedListItems
}

return &typedNestedValue, nil
}

func SetPropertyOfFirstNestedObjectByValueKey[T any](d *schema.ResourceData, propertyKey string, value T) error {
return SetPropertyOfFirstNestedObjectByKey[T](d, propertyKey, "value", value)
}

// SetPropertyOfFirstNestedObjectByKey should be used for single objects defined in the Terraform schema as
// schema.TypeList with MaxItems set to one and inner schema with single value. To easily set
// the inner value, you can specify top-level property with propertyKey, nested value with nestedValueKey and value at the end.
func SetPropertyOfFirstNestedObjectByKey[T any](d *schema.ResourceData, propertyKey string, nestedValueKey string, value T) error {
return d.Set(propertyKey, []any{
map[string]any{
nestedValueKey: value,
},
})
return make([]string, 0)
}

type tags []tag
Expand Down
Loading