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

Additional group attributes #137

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion ad/data_source_ad_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
75 changes: 67 additions & 8 deletions ad/internal/winrmhelper/winrm_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -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)
}
Expand All @@ -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)}
Expand All @@ -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,
Expand Down Expand Up @@ -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"),
Expand All @@ -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,
Expand All @@ -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)
}
Expand All @@ -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 {
Expand All @@ -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
}
95 changes: 95 additions & 0 deletions ad/internal/winrmhelper/winrm_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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
}
3 changes: 1 addition & 2 deletions ad/internal/winrmhelper/winrm_ou.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading