diff --git a/ad/data_source_ad_group.go b/ad/data_source_ad_group.go index fc07b464..5c01681d 100644 --- a/ad/data_source_ad_group.go +++ b/ad/data_source_ad_group.go @@ -72,7 +72,7 @@ func dataSourceADGroup() *schema.Resource { func dataSourceADGroupRead(d *schema.ResourceData, meta interface{}) error { groupID := d.Get("group_id").(string) - g, err := winrmhelper.GetGroupFromHost(meta.(*config.ProviderConf), groupID) + g, err := winrmhelper.GetGroupFromHost(meta.(*config.ProviderConf), groupID, nil) if err != nil { return err } diff --git a/ad/internal/winrmhelper/winrm_group.go b/ad/internal/winrmhelper/winrm_group.go index da7fab33..df14e1b9 100644 --- a/ad/internal/winrmhelper/winrm_group.go +++ b/ad/internal/winrmhelper/winrm_group.go @@ -6,9 +6,9 @@ import ( "log" "strings" - "github.com/hashicorp/terraform-provider-ad/ad/internal/config" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" + "github.com/hashicorp/terraform-provider-ad/ad/internal/config" ) // Group represents an AD Group @@ -23,7 +23,9 @@ type Group struct { Category string Container string Description string + ManagedBy string SID SID `json:"SID"` + CustomAttributes map[string]interface{} } // AddGroup creates a new group @@ -38,6 +40,19 @@ func (g *Group) AddGroup(conf *config.ProviderConf) (string, error) { if g.Description != "" { cmds = append(cmds, fmt.Sprintf("-Description %q", g.Description)) } + + if g.ManagedBy != "" { + cmds = append(cmds, fmt.Sprintf("-ManagedBy %q", g.ManagedBy)) + } + + if g.CustomAttributes != nil { + attrs, err := g.getOtherAttributes() + if err != nil { + return "", err + } + cmds = append(cmds, fmt.Sprintf("-OtherAttributes %s", attrs)) + } + psOpts := CreatePSCommandOpts{ JSONOutput: true, ForceArray: false, @@ -61,7 +76,7 @@ func (g *Group) AddGroup(conf *config.ProviderConf) (string, error) { return "", fmt.Errorf("command New-ADGroup exited with a non-zero exit code %d, stderr: %s", result.ExitCode, result.StdErr) } - group, err := unmarshallGroup([]byte(result.Stdout)) + group, err := unmarshallGroup([]byte(result.Stdout), nil) if err != nil { return "", fmt.Errorf("error while unmarshalling group json document: %s", err) } @@ -76,6 +91,7 @@ func (g *Group) ModifyGroup(d *schema.ResourceData, conf *config.ProviderConf) e "scope": "GroupScope", "category": "GroupCategory", "description": "Description", + "managed_by": "ManagedBy", } cmds := []string{fmt.Sprintf("Set-ADGroup -Identity %q", g.GUID)} @@ -92,6 +108,15 @@ func (g *Group) ModifyGroup(d *schema.ResourceData, conf *config.ProviderConf) e } } + if d.HasChange("custom_attributes") { + oldValue, newValue := d.GetChange("custom_attributes") + otherAttributes, err := GetChangesForCustomAttributes(oldValue, newValue) + if err != nil { + return err + } + cmds = append(cmds, otherAttributes...) + } + if len(cmds) > 1 { psOpts := CreatePSCommandOpts{ JSONOutput: false, @@ -185,8 +210,12 @@ func (g *Group) DeleteGroup(conf *config.ProviderConf) error { return nil } +func (g *Group) getOtherAttributes() (string, error) { + return GetOtherAttributes(g.CustomAttributes) +} + // GetGroupFromResource returns a Group struct built from Resource data -func GetGroupFromResource(d *schema.ResourceData) *Group { +func GetGroupFromResource(d *schema.ResourceData) (*Group, error) { g := Group{ Name: SanitiseTFInput(d, "name"), SAMAccountName: SanitiseTFInput(d, "sam_account_name"), @@ -195,14 +224,25 @@ func GetGroupFromResource(d *schema.ResourceData) *Group { Category: SanitiseTFInput(d, "category"), GUID: SanitiseString(d.Id()), Description: SanitiseTFInput(d, "description"), + ManagedBy: SanitiseTFInput(d, "managed_by"), + } + + ca, ok := d.Get("custom_attributes").(string) + if ok && len(ca) > 0 { + g.CustomAttributes = make(map[string]interface{}) + customAttributes, err := structure.ExpandJsonFromString(ca) + if err != nil { + return nil, fmt.Errorf("while unmarshalling custom attributes JSON doc: %s", err) + } + g.CustomAttributes = customAttributes } - return &g + return &g, nil } // GetGroupFromHost returns a Group struct based on data // retrieved from the AD Controller. -func GetGroupFromHost(conf *config.ProviderConf, guid string) (*Group, error) { +func GetGroupFromHost(conf *config.ProviderConf, guid string, customAttributes []string) (*Group, error) { cmd := fmt.Sprintf("Get-ADGroup -identity %q -properties *", guid) psOpts := CreatePSCommandOpts{ JSONOutput: true, @@ -225,7 +265,7 @@ func GetGroupFromHost(conf *config.ProviderConf, guid string) (*Group, error) { return nil, fmt.Errorf("command Get-ADGroup exited with a non-zero exit code %d, stderr: %s", result.ExitCode, result.StdErr) } - g, err := unmarshallGroup([]byte(result.Stdout)) + g, err := unmarshallGroup([]byte(result.Stdout), customAttributes) if err != nil { return nil, fmt.Errorf("error while unmarshalling group json document: %s", err) } @@ -236,7 +276,7 @@ func GetGroupFromHost(conf *config.ProviderConf, guid string) (*Group, error) { // unmarshallGroup unmarshalls the incoming byte array containing JSON // into a Group structure and populates all fields based on the data // extracted. -func unmarshallGroup(input []byte) (*Group, error) { +func unmarshallGroup(input []byte, customAttributes []string) (*Group, error) { var g Group err := json.Unmarshal(input, &g) if err != nil { @@ -255,5 +295,24 @@ func unmarshallGroup(input []byte) (*Group, error) { commaIdx := strings.Index(g.DistinguishedName, ",") g.Container = g.DistinguishedName[commaIdx+1:] + if customAttributes == nil { + return &g, nil + } + + var groupMapIntf interface{} + err = json.Unmarshal(input, &groupMapIntf) + if err != nil { + log.Printf("[DEBUG] Failed to unmarshall json document with error %q, document was: %s", err, string(input)) + return nil, fmt.Errorf("failed while unmarshalling json response: %s", err) + } + + groupMap := groupMapIntf.(map[string]interface{}) + g.CustomAttributes = make(map[string]interface{}) + for _, property := range customAttributes { + if val, ok := groupMap[property]; ok { + g.CustomAttributes[property] = val + } + } + return &g, nil } diff --git a/ad/internal/winrmhelper/winrm_helper.go b/ad/internal/winrmhelper/winrm_helper.go index 3fe28913..f4875d29 100644 --- a/ad/internal/winrmhelper/winrm_helper.go +++ b/ad/internal/winrmhelper/winrm_helper.go @@ -13,6 +13,7 @@ import ( "syscall" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" "github.com/hashicorp/terraform-provider-ad/ad/internal/config" "github.com/packer-community/winrmcp/winrmcp" ) @@ -223,3 +224,97 @@ func UploadFiletoSYSVOL(conf *config.ProviderConf, cpClient *winrmcp.Winrmcp, bu return nil } + +func GetOtherAttributes(customAttributes map[string]interface{}) (string, error) { + out := []string{} + for k, v := range customAttributes { + cleanKey := SanitiseString(k) + var cleanValue string + if reflect.ValueOf(v).Kind() == reflect.Slice { + quotedStrings := make([]string, len(v.([]interface{}))) + for idx, s := range v.([]interface{}) { + // Using %q here will cause double quotes inside the string to be escaped with \" + // which is not desirable in Powershell + quotedStrings[idx] = GetString(s.(string)) + } + cleanValue = strings.Join(quotedStrings, ",") + } else { + cleanValue = GetString(v.(string)) + } + out = append(out, fmt.Sprintf(`'%s'=%s`, cleanKey, cleanValue)) + } + finalAttrString := strings.Join(out, ";") + return fmt.Sprintf("@{%s}", finalAttrString), nil +} + +func GetChangesForCustomAttributes(oldValue, newValue interface{}) ([]string, error) { + cmds := []string{} + newMap, err := structure.ExpandJsonFromString(newValue.(string)) + if err != nil { + return nil, err + } + newSortedMap := SortInnerSlice(newMap) + toClear := []string{} + toReplace := []string{} + toAdd := []string{} + + var oldSortedMap map[string]interface{} + if oldValue.(string) != "" { + oldMap, err := structure.ExpandJsonFromString(oldValue.(string)) + if err != nil { + return nil, fmt.Errorf("while expanding CA json string %s: %s", oldValue.(string), err) + } + oldSortedMap = SortInnerSlice(oldMap) + } + + for k, v := range oldSortedMap { + if newVal, ok := newSortedMap[k]; ok { + if !reflect.DeepEqual(v, newVal) { + var out string + if reflect.ValueOf(newVal).Kind() == reflect.Slice { + quotedStrings := make([]string, len(newVal.([]string))) + for idx, s := range newVal.([]string) { + quotedStrings[idx] = s + } + out = strings.Join(quotedStrings, ",") + } else { + out = newVal.(string) + } + toReplace = append(toReplace, fmt.Sprintf("%s=%s", SanitiseString(k), out)) + } + } else { + toClear = append(toClear, SanitiseString(k)) + } + } + + for k, newVal := range newSortedMap { + if _, ok := oldSortedMap[k]; !ok { + var out string + if reflect.ValueOf(newVal).Kind() == reflect.Slice { + quotedStrings := make([]string, len(newVal.([]string))) + for idx, s := range newVal.([]string) { + // Using %q here will cause double quotes inside the string to be escaped with \" + // which is not desirable in Powershell + quotedStrings[idx] = s + } + out = strings.Join(quotedStrings, ",") + } else { + out = newVal.(string) + } + toAdd = append(toAdd, fmt.Sprintf("%s=%s", SanitiseString(k), out)) + } + } + + if len(toClear) > 0 { + cmds = append(cmds, fmt.Sprintf(`-Clear %s`, strings.Join(toClear, ","))) + } + + if len(toReplace) > 0 { + cmds = append(cmds, fmt.Sprintf(`-Replace @{%s}`, strings.Join(toReplace, ";"))) + } + + if len(toAdd) > 0 { + cmds = append(cmds, fmt.Sprintf(`-Add @{%s}`, strings.Join(toAdd, ";"))) + } + return cmds, nil +} diff --git a/ad/internal/winrmhelper/winrm_ou.go b/ad/internal/winrmhelper/winrm_ou.go index c4cca929..89c2c627 100644 --- a/ad/internal/winrmhelper/winrm_ou.go +++ b/ad/internal/winrmhelper/winrm_ou.go @@ -6,9 +6,8 @@ import ( "log" "strings" - "github.com/hashicorp/terraform-provider-ad/ad/internal/config" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-ad/ad/internal/config" ) // OrgUnit is a structure used to represent an AD OrganizationalUnit object diff --git a/ad/internal/winrmhelper/winrm_user.go b/ad/internal/winrmhelper/winrm_user.go index 6d68c71a..b2fd7f0f 100644 --- a/ad/internal/winrmhelper/winrm_user.go +++ b/ad/internal/winrmhelper/winrm_user.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "log" - "reflect" "strings" "github.com/hashicorp/terraform-provider-ad/ad/internal/config" @@ -311,77 +310,11 @@ func (u *User) ModifyUser(d *schema.ResourceData, conf *config.ProviderConf) err if d.HasChange("custom_attributes") { oldValue, newValue := d.GetChange("custom_attributes") - newMap, err := structure.ExpandJsonFromString(newValue.(string)) + otherAttributes, err := GetChangesForCustomAttributes(oldValue, newValue) if err != nil { return err } - - newSortedMap := SortInnerSlice(newMap) - toClear := []string{} - toReplace := []string{} - toAdd := []string{} - - var oldSortedMap map[string]interface{} - if oldValue.(string) != "" { - oldMap, err := structure.ExpandJsonFromString(oldValue.(string)) - if err != nil { - return fmt.Errorf("while expanding CA json string %s: %s", oldValue.(string), err) - } - oldSortedMap = SortInnerSlice(oldMap) - } - - for k, v := range oldSortedMap { - if newVal, ok := newSortedMap[k]; ok { - if !reflect.DeepEqual(v, newVal) { - var out string - if reflect.ValueOf(newVal).Kind() == reflect.Slice { - quotedStrings := make([]string, len(newVal.([]string))) - for idx, s := range newVal.([]string) { - // Using %q here will cause double quotes inside the string to be escaped with \" - // which is not desirable in Powershell - quotedStrings[idx] = fmt.Sprintf(`"%s"`, s) - } - out = strings.Join(quotedStrings, ",") - } else { - out = fmt.Sprintf(`"%s"`, newVal.(string)) - } - toReplace = append(toReplace, fmt.Sprintf("%s=%s", SanitiseString(k), out)) - } - } else { - toClear = append(toClear, SanitiseString(k)) - } - } - - for k, newVal := range newSortedMap { - if _, ok := oldSortedMap[k]; !ok { - var out string - if reflect.ValueOf(newVal).Kind() == reflect.Slice { - quotedStrings := make([]string, len(newVal.([]string))) - for idx, s := range newVal.([]string) { - // Using %q here will cause double quotes inside the string to be escaped with \" - // which is not desirable in Powershell - quotedStrings[idx] = s - } - out = strings.Join(quotedStrings, ",") - } else { - out = newVal.(string) - } - toAdd = append(toAdd, fmt.Sprintf("%s=%s", SanitiseString(k), out)) - } - } - - if len(toClear) > 0 { - cmds = append(cmds, fmt.Sprintf(`-Clear %s`, strings.Join(toClear, ";"))) - } - - if len(toReplace) > 0 { - cmds = append(cmds, fmt.Sprintf(`-Replace @{%s}`, strings.Join(toReplace, ";"))) - } - - if len(toAdd) > 0 { - cmds = append(cmds, fmt.Sprintf(`-Add @{%s}`, strings.Join(toAdd, ";"))) - } - + cmds = append(cmds, otherAttributes...) } if len(cmds) > 1 { @@ -478,25 +411,7 @@ func (u *User) DeleteUser(conf *config.ProviderConf) error { } func (u *User) getOtherAttributes() (string, error) { - out := []string{} - for k, v := range u.CustomAttributes { - cleanKey := SanitiseString(k) - var cleanValue string - if reflect.ValueOf(v).Kind() == reflect.Slice { - quotedStrings := make([]string, len(v.([]interface{}))) - for idx, s := range v.([]interface{}) { - // Using %q here will cause double quotes inside the string to be escaped with \" - // which is not desirable in Powershell - quotedStrings[idx] = GetString(s.(string)) - } - cleanValue = strings.Join(quotedStrings, ",") - } else { - cleanValue = GetString(v.(string)) - } - out = append(out, fmt.Sprintf(`'%s'=%s`, cleanKey, cleanValue)) - } - finalAttrString := strings.Join(out, ";") - return fmt.Sprintf("@{%s}", finalAttrString), nil + return GetOtherAttributes(u.CustomAttributes) } // GetUserFromResource returns a user struct built from Resource data diff --git a/ad/resource_ad_group.go b/ad/resource_ad_group.go index 55e505af..39875ff5 100644 --- a/ad/resource_ad_group.go +++ b/ad/resource_ad_group.go @@ -1,12 +1,15 @@ package ad import ( + "context" "fmt" "strings" "github.com/hashicorp/terraform-provider-ad/ad/internal/config" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-ad/ad/internal/winrmhelper" ) @@ -21,6 +24,12 @@ func resourceADGroup() *schema.Resource { Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, + CustomizeDiff: customdiff.All( + customdiff.ComputedIf("dn", func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) bool { + // Changing the name (CN) or container (OU) of the group, changes the distinguishedName as well + return d.HasChange("name") || d.HasChange("container") + }), + ), Schema: map[string]*schema.Schema{ "name": { Type: schema.TypeString, @@ -57,6 +66,19 @@ func resourceADGroup() *schema.Resource { Optional: true, Description: "Description of the Group.", }, + "managed_by": { + Type: schema.TypeString, + Optional: true, + Description: "The distinguished name of the user or group that is assigned to manage this object.", + }, + "custom_attributes": { + Type: schema.TypeString, + Optional: true, + Description: "JSON encoded map that represents key/value pairs for custom attributes. Please note that `terraform import` will not import these attributes.", + ValidateFunc: validation.StringIsJSON, + DiffSuppressFunc: suppressJsonDiff, + Default: "{}", + }, "sid": { Type: schema.TypeString, Computed: true, @@ -72,8 +94,11 @@ func resourceADGroup() *schema.Resource { } func resourceADGroupCreate(d *schema.ResourceData, meta interface{}) error { - u := winrmhelper.GetGroupFromResource(d) - guid, err := u.AddGroup(meta.(*config.ProviderConf)) + g, err := winrmhelper.GetGroupFromResource(d) + if err != nil { + return err + } + guid, err := g.AddGroup(meta.(*config.ProviderConf)) if err != nil { return err } @@ -82,7 +107,11 @@ func resourceADGroupCreate(d *schema.ResourceData, meta interface{}) error { } func resourceADGroupRead(d *schema.ResourceData, meta interface{}) error { - g, err := winrmhelper.GetGroupFromHost(meta.(*config.ProviderConf), d.Id()) + caKeys, err := extractCustAttrKeys(d) + if err != nil { + return err + } + g, err := winrmhelper.GetGroupFromHost(meta.(*config.ProviderConf), d.Id(), caKeys) if err != nil { if strings.Contains(err.Error(), "ADIdentityNotFoundException") { d.SetId("") @@ -94,12 +123,20 @@ func resourceADGroupRead(d *schema.ResourceData, meta interface{}) error { d.SetId("") return nil } + if g.CustomAttributes != nil { + ca, err := structure.FlattenJsonToString(g.CustomAttributes) + if err != nil { + return err + } + _ = d.Set("custom_attributes", ca) + } _ = d.Set("sam_account_name", g.SAMAccountName) _ = d.Set("name", g.Name) _ = d.Set("scope", g.Scope) _ = d.Set("category", g.Category) _ = d.Set("container", g.Container) _ = d.Set("description", g.Description) + _ = d.Set("managed_by", g.ManagedBy) _ = d.Set("dn", g.DistinguishedName) _ = d.Set("sid", g.SID.Value) @@ -107,8 +144,11 @@ func resourceADGroupRead(d *schema.ResourceData, meta interface{}) error { } func resourceADGroupUpdate(d *schema.ResourceData, meta interface{}) error { - g := winrmhelper.GetGroupFromResource(d) - err := g.ModifyGroup(d, meta.(*config.ProviderConf)) + g, err := winrmhelper.GetGroupFromResource(d) + if err != nil { + return err + } + err = g.ModifyGroup(d, meta.(*config.ProviderConf)) if err != nil { return err } @@ -116,7 +156,7 @@ func resourceADGroupUpdate(d *schema.ResourceData, meta interface{}) error { } func resourceADGroupDelete(d *schema.ResourceData, meta interface{}) error { - g, err := winrmhelper.GetGroupFromHost(meta.(*config.ProviderConf), d.Id()) + g, err := winrmhelper.GetGroupFromHost(meta.(*config.ProviderConf), d.Id(), nil) if err != nil { if strings.Contains(err.Error(), "ADIdentityNotFoundException") { return nil diff --git a/ad/resource_ad_group_test.go b/ad/resource_ad_group_test.go index 8d5e55ac..15dbc720 100644 --- a/ad/resource_ad_group_test.go +++ b/ad/resource_ad_group_test.go @@ -151,7 +151,7 @@ func testAccResourceADGroupExists(name, groupSAM string, expected bool) resource } defer conf.ReleaseWinRMClient(client) - u, err := winrmhelper.GetGroupFromHost(conf, rs.Primary.ID) + u, err := winrmhelper.GetGroupFromHost(conf, rs.Primary.ID, nil) if err != nil { if strings.Contains(err.Error(), "ADIdentityNotFoundException") && !expected { return nil diff --git a/ad/resource_ad_user.go b/ad/resource_ad_user.go index 49612b21..6e3dde7b 100644 --- a/ad/resource_ad_user.go +++ b/ad/resource_ad_user.go @@ -225,6 +225,7 @@ func resourceADUser() *schema.Resource { Description: "JSON encoded map that represents key/value pairs for custom attributes. Please note that `terraform import` will not import these attributes.", ValidateFunc: validation.StringIsJSON, DiffSuppressFunc: suppressJsonDiff, + Default: "{}", }, "sid": { Type: schema.TypeString, @@ -241,7 +242,10 @@ func resourceADUser() *schema.Resource { } func suppressJsonDiff(k, old, new string, d *schema.ResourceData) bool { - + // Avoid permadiff when an empty map json is passed + if old == "" && new == "{}" { + return true + } oldMap, err := structure.ExpandJsonFromString(old) if err != nil { return false diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff/compose.go b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff/compose.go new file mode 100644 index 00000000..9b1b929d --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff/compose.go @@ -0,0 +1,75 @@ +package customdiff + +import ( + "context" + + "github.com/hashicorp/go-multierror" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// All returns a CustomizeDiffFunc that runs all of the given +// CustomizeDiffFuncs and returns all of the errors produced. +// +// If one function produces an error, functions after it are still run. +// If this is not desirable, use function Sequence instead. +// +// If multiple functions returns errors, the result is a multierror. +// +// For example: +// +// &schema.Resource{ +// // ... +// CustomizeDiff: customdiff.All( +// customdiff.ValidateChange("size", func (old, new, meta interface{}) error { +// // If we are increasing "size" then the new value must be +// // a multiple of the old value. +// if new.(int) <= old.(int) { +// return nil +// } +// if (new.(int) % old.(int)) != 0 { +// return fmt.Errorf("new size value must be an integer multiple of old value %d", old.(int)) +// } +// return nil +// }), +// customdiff.ForceNewIfChange("size", func (old, new, meta interface{}) bool { +// // "size" can only increase in-place, so we must create a new resource +// // if it is decreased. +// return new.(int) < old.(int) +// }), +// customdiff.ComputedIf("version_id", func (d *schema.ResourceDiff, meta interface{}) bool { +// // Any change to "content" causes a new "version_id" to be allocated. +// return d.HasChange("content") +// }), +// ), +// } +// +func All(funcs ...schema.CustomizeDiffFunc) schema.CustomizeDiffFunc { + return func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error { + var err error + for _, f := range funcs { + thisErr := f(ctx, d, meta) + if thisErr != nil { + err = multierror.Append(err, thisErr) + } + } + return err + } +} + +// Sequence returns a CustomizeDiffFunc that runs all of the given +// CustomizeDiffFuncs in sequence, stopping at the first one that returns +// an error and returning that error. +// +// If all functions succeed, the combined function also succeeds. +func Sequence(funcs ...schema.CustomizeDiffFunc) schema.CustomizeDiffFunc { + return func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error { + for _, f := range funcs { + err := f(ctx, d, meta) + if err != nil { + return err + } + } + return nil + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff/computed.go b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff/computed.go new file mode 100644 index 00000000..f47d11a4 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff/computed.go @@ -0,0 +1,18 @@ +package customdiff + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// ComputedIf returns a CustomizeDiffFunc that sets the given key's new value +// as computed if the given condition function returns true. +func ComputedIf(key string, f ResourceConditionFunc) schema.CustomizeDiffFunc { + return func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error { + if f(ctx, d, meta) { + d.SetNewComputed(key) + } + return nil + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff/condition.go b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff/condition.go new file mode 100644 index 00000000..e85cdc7a --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff/condition.go @@ -0,0 +1,62 @@ +package customdiff + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// ResourceConditionFunc is a function type that makes a boolean decision based +// on an entire resource diff. +type ResourceConditionFunc func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) bool + +// ValueChangeConditionFunc is a function type that makes a boolean decision +// by comparing two values. +type ValueChangeConditionFunc func(ctx context.Context, old, new, meta interface{}) bool + +// ValueConditionFunc is a function type that makes a boolean decision based +// on a given value. +type ValueConditionFunc func(ctx context.Context, value, meta interface{}) bool + +// If returns a CustomizeDiffFunc that calls the given condition +// function and then calls the given CustomizeDiffFunc only if the condition +// function returns true. +// +// This can be used to include conditional customizations when composing +// customizations using All and Sequence, but should generally be used only in +// simple scenarios. Prefer directly writing a CustomizeDiffFunc containing +// a conditional branch if the given CustomizeDiffFunc is already a +// locally-defined function, since this avoids obscuring the control flow. +func If(cond ResourceConditionFunc, f schema.CustomizeDiffFunc) schema.CustomizeDiffFunc { + return func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error { + if cond(ctx, d, meta) { + return f(ctx, d, meta) + } + return nil + } +} + +// IfValueChange returns a CustomizeDiffFunc that calls the given condition +// function with the old and new values of the given key and then calls the +// given CustomizeDiffFunc only if the condition function returns true. +func IfValueChange(key string, cond ValueChangeConditionFunc, f schema.CustomizeDiffFunc) schema.CustomizeDiffFunc { + return func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error { + old, new := d.GetChange(key) + if cond(ctx, old, new, meta) { + return f(ctx, d, meta) + } + return nil + } +} + +// IfValue returns a CustomizeDiffFunc that calls the given condition +// function with the new values of the given key and then calls the +// given CustomizeDiffFunc only if the condition function returns true. +func IfValue(key string, cond ValueConditionFunc, f schema.CustomizeDiffFunc) schema.CustomizeDiffFunc { + return func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error { + if cond(ctx, d.Get(key), meta) { + return f(ctx, d, meta) + } + return nil + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff/doc.go b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff/doc.go new file mode 100644 index 00000000..c6ad1199 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff/doc.go @@ -0,0 +1,11 @@ +// Package customdiff provides a set of reusable and composable functions +// to enable more "declarative" use of the CustomizeDiff mechanism available +// for resources in package helper/schema. +// +// The intent of these helpers is to make the intent of a set of diff +// customizations easier to see, rather than lost in a sea of Go function +// boilerplate. They should _not_ be used in situations where they _obscure_ +// intent, e.g. by over-using the composition functions where a single +// function containing normal Go control flow statements would be more +// straightforward. +package customdiff diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff/force_new.go b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff/force_new.go new file mode 100644 index 00000000..0ffc0886 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff/force_new.go @@ -0,0 +1,42 @@ +package customdiff + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// ForceNewIf returns a CustomizeDiffFunc that flags the given key as +// requiring a new resource if the given condition function returns true. +// +// The return value of the condition function is ignored if the old and new +// values of the field compare equal, since no attribute diff is generated in +// that case. +func ForceNewIf(key string, f ResourceConditionFunc) schema.CustomizeDiffFunc { + return func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error { + if f(ctx, d, meta) { + d.ForceNew(key) + } + return nil + } +} + +// ForceNewIfChange returns a CustomizeDiffFunc that flags the given key as +// requiring a new resource if the given condition function returns true. +// +// The return value of the condition function is ignored if the old and new +// values compare equal, since no attribute diff is generated in that case. +// +// This function is similar to ForceNewIf but provides the condition function +// only the old and new values of the given key, which leads to more compact +// and explicit code in the common case where the decision can be made with +// only the specific field value. +func ForceNewIfChange(key string, f ValueChangeConditionFunc) schema.CustomizeDiffFunc { + return func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error { + old, new := d.GetChange(key) + if f(ctx, old, new, meta) { + d.ForceNew(key) + } + return nil + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff/validate.go b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff/validate.go new file mode 100644 index 00000000..5b88e5e0 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff/validate.go @@ -0,0 +1,40 @@ +package customdiff + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// ValueChangeValidationFunc is a function type that validates the difference +// (or lack thereof) between two values, returning an error if the change +// is invalid. +type ValueChangeValidationFunc func(ctx context.Context, old, new, meta interface{}) error + +// ValueValidationFunc is a function type that validates a particular value, +// returning an error if the value is invalid. +type ValueValidationFunc func(ctx context.Context, value, meta interface{}) error + +// ValidateChange returns a CustomizeDiffFunc that applies the given validation +// function to the change for the given key, returning any error produced. +func ValidateChange(key string, f ValueChangeValidationFunc) schema.CustomizeDiffFunc { + return func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error { + old, new := d.GetChange(key) + return f(ctx, old, new, meta) + } +} + +// ValidateValue returns a CustomizeDiffFunc that applies the given validation +// function to value of the given key, returning any error produced. +// +// This should generally not be used since it is functionally equivalent to +// a validation function applied directly to the schema attribute in question, +// but is provided for situations where composing multiple CustomizeDiffFuncs +// together makes intent clearer than spreading that validation across the +// schema. +func ValidateValue(key string, f ValueValidationFunc) schema.CustomizeDiffFunc { + return func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error { + val := d.Get(key) + return f(ctx, val, meta) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure/flatten_json.go b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure/flatten_json.go index d0913ac6..cb6a014f 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure/flatten_json.go +++ b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure/flatten_json.go @@ -7,12 +7,12 @@ import "encoding/json" func FlattenJsonToString(input map[string]interface{}) (string, error) { if len(input) == 0 { - return "", nil + return "{}", nil } result, err := json.Marshal(input) if err != nil { - return "", err + return "{}", err } return string(result), nil diff --git a/vendor/modules.txt b/vendor/modules.txt index 6c59c029..345d9b2a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -183,6 +183,7 @@ github.com/hashicorp/terraform-plugin-log/tfsdklog # github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0 ## explicit; go 1.21 github.com/hashicorp/terraform-plugin-sdk/v2/diag +github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff github.com/hashicorp/terraform-plugin-sdk/v2/helper/id github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource