deployment/managementgroup.go (491 lines of code) (raw):

// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package deployment import ( "context" "encoding/json" "fmt" "strings" "github.com/Azure/alzlib" "github.com/Azure/alzlib/assets" "github.com/Azure/alzlib/to" "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armpolicy" "github.com/brunoga/deep" mapset "github.com/deckarep/golang-set/v2" "github.com/matt-FFFFFF/goarmfunctions" ) // HierarchyManagementGroup represents an Azure Management Group within a hierarchy, with links to parent and children. type HierarchyManagementGroup struct { children mapset.Set[*HierarchyManagementGroup] // The children of the management group. displayName string // The display name of the management group. exists bool // Whether the management group already exists in the hierarchy. hierarchy *Hierarchy // The hierarchy that the management group belongs to. id string // The name of the management group, forming the last part of the resource id. level int // The level of the management group in the hierarchy. location string // The default location to use for artifacts in the management group. parent *HierarchyManagementGroup // The internal parent management group - will be nil if parent is external. parentExternal *string // The external parent management group - will be nil if parent is internal. policyAssignments map[string]*assets.PolicyAssignment // The policy assignments in the management group. policyDefinitions map[string]*assets.PolicyDefinition // The policy definitions in the management group. policyRoleAssignments mapset.Set[PolicyRoleAssignment] // The additional role assignments needed for the policy assignments. policySetDefinitions map[string]*assets.PolicySetDefinition // The policy set definitions in the management group. roleDefinitions map[string]*assets.RoleDefinition // The role definitions in the management group. } // managementGroupAddRequest represents the request to add a management group to the hierarchy. type managementGroupAddRequest struct { id string // The name of the management group, forming the last part of the resource id. displayName string // The display name of the management group. exists bool // Whether the management group already exists in the hierarchy. parentId string // The name of the parent management group. parentIsExternal bool // If true, the parent management group is external to the hierarchy. archetypes []*alzlib.Archetype // The archetypes to use for the management group. level int // The level of the management group in the hierarchy. location string // The default location to use for artifacts in the management group. } // PolicyRoleAssignment represents the role assignments that need to be created for a management group. // Since we could be using system assigned identities, we don't know the principal ID until after the deployment. // Therefore this data can be used to create the role assignments after the deployment. type PolicyRoleAssignment struct { RoleDefinitionId string `json:"role_definition_id,omitempty"` Scope string `json:"scope,omitempty"` AssignmentName string `json:"assignment_name,omitempty"` ManagementGroupId string `json:"management_group_id,omitempty"` } // Children returns the children of the management group. func (alzmg *HierarchyManagementGroup) Children() []*HierarchyManagementGroup { return alzmg.children.ToSlice() } // DisplayName returns the display name of the management group. func (mg *HierarchyManagementGroup) DisplayName() string { return mg.displayName } // Name returns the name/id of the management group. func (mg *HierarchyManagementGroup) Name() string { return mg.id } // HasParent returns a bool value depending on whether the management group has a given parent. // Only works for internal parents. func (mg *HierarchyManagementGroup) HasParent(id string) bool { if mg.parentExternal != nil || mg.parent == nil { return false } if mg.parent.id == id { return true } return mg.parent.HasParent(id) } // ParentId returns the ID of the parent management group. // If the parent is external, this will be preferred. // If neither are set an empty string is returned (though this should never happen). func (mg *HierarchyManagementGroup) ParentId() string { if mg.parentExternal != nil { return *mg.parentExternal } if mg.parent != nil { return mg.parent.id } return "" } // Parent returns parent *AlzManagementGroup. // If the parent is external, the result will be nil. func (mg *HierarchyManagementGroup) Parent() *HierarchyManagementGroup { if mg.parentExternal != nil { return nil } return mg.parent } // ParentIsExternal returns a bool value depending on whether the parent MG is external or not. func (mg *HierarchyManagementGroup) ParentIsExternal() bool { if mg.parentExternal != nil && *mg.parentExternal != "" { return true } return false } // Exists returns a bool value depending on whether the management group exists. func (mg *HierarchyManagementGroup) Exists() bool { return mg.exists } // Level returns the level of the management group in the hierarchy. func (mg *HierarchyManagementGroup) Level() int { return mg.level } // Location returns the default location to use for artifacts in the management group. func (mg *HierarchyManagementGroup) Location() string { return mg.location } // ResourceId returns the resource ID of the management group. func (mg *HierarchyManagementGroup) ResourceId() string { return fmt.Sprintf(ManagementGroupIdFmt, mg.id) } // PolicyAssignmentMap returns a copy of the policy assignments map. func (mg *HierarchyManagementGroup) PolicyAssignmentMap() map[string]*assets.PolicyAssignment { return copyMap[string, *assets.PolicyAssignment](mg.policyAssignments) } // PolicyDefinitionsMap returns a copy of the policy definitions map. func (mg *HierarchyManagementGroup) PolicyDefinitionsMap() map[string]*assets.PolicyDefinition { return copyMap[string, *assets.PolicyDefinition](mg.policyDefinitions) } // PolicySetDefinitionsMap returns a copy of the policy definitions map. func (mg *HierarchyManagementGroup) PolicySetDefinitionsMap() map[string]*assets.PolicySetDefinition { return copyMap[string, *assets.PolicySetDefinition](mg.policySetDefinitions) } // RoleDefinitionsMap returns a copy of the role definitions map. func (alzmg *HierarchyManagementGroup) RoleDefinitionsMap() map[string]*assets.RoleDefinition { return copyMap[string, *assets.RoleDefinition](alzmg.roleDefinitions) } // generatePolicyAssignmentAdditionalRoleAssignments generates the additional role assignment data needed for the policy assignments // It should be run once the policy assignments map has been fully populated for a given HierarchyManagementGroup. // It will iterate through all policy assignments and generate the additional role assignments for each one, // storing them in the AdditionalRoleAssignmentsByPolicyAssignment map. func (mg *HierarchyManagementGroup) generatePolicyAssignmentAdditionalRoleAssignments() error { // Make a error collection type so we can return them in the error message without stopping the process. // Upstream code can then decide what to do with them, issue warnings in stead of hard fail, etc. var errs *PolicyRoleAssignmentErrors for paName, pa := range mg.policyAssignments { // we only care about policy assignments that use an identity if pa.IdentityType() == armpolicy.ResourceIdentityTypeNone { continue } // get the policy definition name using the resource id policyDefinitionRef, err := pa.ReferencedPolicyDefinitionResourceId() if err != nil { return fmt.Errorf("ManagementGroup.GeneratePolicyAssignmentAdditionalRoleAssignments: error getting referenced policy definition type for policy assignment `%s`: %w", paName, err) } switch policyDefinitionRef.ResourceType.Type { case "policyDefinitions": // check the definition exists in the AlzLib pd := mg.hierarchy.alzlib.PolicyDefinition(policyDefinitionRef.Name) if pd == nil { return fmt.Errorf("ManagementGroup.GeneratePolicyAssignmentAdditionalRoleAssignments: policy definition `%s`, referenced by `%s` not found in AlzLib", policyDefinitionRef.Name, paName) } // get the role definition ids from the policy definition and add to the additional role assignment data rdids, err := pd.NormalizedRoleDefinitionResourceIds() if err != nil { return fmt.Errorf("ManagementGroup.GeneratePolicyAssignmentAdditionalRoleAssignments: assignment `%s`, error getting role definition ids for policy definition `%s`: %w", paName, *pd.Name, err) } if len(rdids) == 0 { return fmt.Errorf("ManagementGroup.GeneratePolicyAssignmentAdditionalRoleAssignments: assignment `%s`, policy definition `%s` has no role definition ids", paName, *pd.Name) } for _, rdid := range rdids { mg.policyRoleAssignments.Add(PolicyRoleAssignment{ Scope: mg.ResourceId(), RoleDefinitionId: rdid, AssignmentName: paName, ManagementGroupId: mg.id, }) } // for each parameter with assignPermissions = true // add the additional role assignment data unless the parameter value is empty assignPermissionParams, err := pd.AssignPermissionsParameterNames() if err != nil { return fmt.Errorf("ManagementGroup.GeneratePolicyAssignmentAdditionalRoleAssignments: error getting assign permissions parameter names for policy definition `%s`: %w", *pd.Name, err) } for _, paramName := range assignPermissionParams { paramIsOptional, err := pd.ParameterIsOptional(paramName) if err != nil { return fmt.Errorf("ManagementGroup.GeneratePolicyAssignmentAdditionalRoleAssignments: error getting parameter %s optional status for policy definition `%s`: %w", paramName, *pd.Name, err) } paParamVal, err := pa.ParameterValueAsString(paramName) if err != nil && !paramIsOptional { return fmt.Errorf("ManagementGroup.GeneratePolicyAssignmentAdditionalRoleAssignments: error getting parameter value for parameter `%s` in policy assignment `%s`: %w", paramName, paName, err) } // We should assign permissions but the parameter os optional and doesn't have a value in the assignment, so skip. if err != nil && paramIsOptional { continue } if paParamVal == "" { continue } resId, err := arm.ParseResourceID(paParamVal) if err != nil { continue } for _, rdid := range rdids { mg.policyRoleAssignments.Add(PolicyRoleAssignment{ Scope: resId.String(), RoleDefinitionId: rdid, AssignmentName: paName, ManagementGroupId: mg.id, }) } } case "policySetDefinitions": psd := mg.hierarchy.alzlib.PolicySetDefinition(policyDefinitionRef.Name) if psd == nil { return fmt.Errorf("ManagementGroup.GeneratePolicyAssignmentAdditionalRoleAssignments: assignment `%s`, policy set `%s` not found in AlzLib", paName, policyDefinitionRef.Name) } pdRefs := psd.PolicyDefinitionReferences() if pdRefs == nil { return fmt.Errorf("ManagementGroup.GeneratePolicyAssignmentAdditionalRoleAssignments: assignment `%s`, error getting referenced policy definition names for policy set definition %s", paName, *psd.Name) } // for each policy definition in the policy set definition for _, pdRef := range pdRefs { pdName, err := assets.NameFromResourceId(*pdRef.PolicyDefinitionID) if err != nil { return fmt.Errorf("ManagementGroup.GeneratePolicyAssignmentAdditionalRoleAssignments: assignment `%s`, error getting policy definition name from policy set definition `%s`, with id `%s`: %w", paName, *psd.Name, *pdRef.PolicyDefinitionID, err) } pd := mg.hierarchy.alzlib.PolicyDefinition(pdName) if pd == nil { return fmt.Errorf("ManagementGroup.GeneratePolicyAssignmentAdditionalRoleAssignments: assignment `%s`, policy definition `%s`, referenced by `%s` not found in AlzLib", paName, pdName, *psd.Name) } // get the role definition ids from the policy definition and add to the additional role assignment data rdids, err := pd.NormalizedRoleDefinitionResourceIds() if err != nil { return fmt.Errorf("ManagementGroup.GeneratePolicyAssignmentAdditionalRoleAssignments: assignment `%s`, error getting role definition ids referenced in policy set `%s` for policy definition %s: %w", paName, *psd.Name, pdName, err) } for _, rdid := range rdids { mg.policyRoleAssignments.Add(PolicyRoleAssignment{ Scope: mg.ResourceId(), RoleDefinitionId: rdid, AssignmentName: paName, ManagementGroupId: mg.id, }) } // for each parameter with assignPermissions = true // add the additional scopes to the additional role assignment data // to do this we have to map the assignment parameter value to the policy definition parameter value. for paramName, paramVal := range pd.Properties.Parameters { // If assignPermissions is not set then skip. if paramVal.Metadata == nil || paramVal.Metadata.AssignPermissions == nil || !*paramVal.Metadata.AssignPermissions { continue } // get the parameter value from the policy reference within the set definition. if _, ok := pd.Properties.Parameters[paramName]; !ok { return fmt.Errorf("ManagementGroup.GeneratePolicyAssignmentAdditionalRoleAssignments: assignment `%s` for policy set `%s`, parameter `%s` not found in refernced policy definition `%s`", paName, *psd.Name, paramName, *pd.Name) } // use goarmfunctions to evaluate the ARM expression in the parameter value in the set definition reference. scope, err := parseArmFunctionInPolicySetParameter(*pdRef.PolicyDefinitionReferenceID, paramName, &pa.Assignment, &psd.SetDefinition) if err != nil { if errs == nil { errs = NewPolicyRoleAssignmentErrors() } errs.Add(NewPolicyRoleAssignmentError(paName, mg.id, paramName, *pdRef.PolicyDefinitionReferenceID, rdids, err)) continue } // The value should be a string. scopeStr, ok := scope.(string) if !ok { if errs == nil { errs = NewPolicyRoleAssignmentErrors() } errs.Add(NewPolicyRoleAssignmentError(paName, mg.id, paramName, *pdRef.PolicyDefinitionReferenceID, rdids, fmt.Errorf("ManagementGroup.GeneratePolicyAssignmentAdditionalRoleAssignments: assignment `%s` for policy set `%s`, parameter `%s` value in policy definition `%s` is not a string", paName, *psd.Name, paramName, *pd.Name))) continue } // The value should be an ARM resource ID. resid, err := arm.ParseResourceID(scopeStr) if err != nil { if errs == nil { errs = NewPolicyRoleAssignmentErrors() } errs.Add(NewPolicyRoleAssignmentError(paName, mg.id, paramName, *pdRef.PolicyDefinitionReferenceID, rdids, err)) continue } // if we got this far then we can add the role assignments. for _, rdid := range rdids { mg.policyRoleAssignments.Add(PolicyRoleAssignment{ Scope: resid.String(), RoleDefinitionId: rdid, AssignmentName: paName, ManagementGroupId: mg.id, }) } } } } } if errs != nil { return errs } return nil } // update will update the AlzManagementGroup resources with the correct resource ids, references, etc. // Make sure to pass in any updates to the policy assignment parameter values. func (mg *HierarchyManagementGroup) update() error { pd2mg := mg.hierarchy.policyDefinitionToMg() psd2mg := mg.hierarchy.policySetDefinitionToMg() // re-write the policy definition ID property to be the current MG name. updatePolicyDefinitions(mg) // re-write the policy set definition ID property and go through the referenced definitions // and write the definition id if it's custom. if err := updatePolicySetDefinitions(mg, pd2mg); err != nil { return fmt.Errorf("HierarchyManagementGroup.update: error updating policy set definitions for mg `%s`: %w", mg.id, err) } // re-write the assignableScopes for the role definitions. updateRoleDefinitions(mg) if err := updatePolicyAsignments(mg, pd2mg, psd2mg); err != nil { return fmt.Errorf("HierarchyManagementGroup.update: error updating policy assignments: %w", err) } return nil } // ModifyPolicyAssignment modifies an existing policy assignment in the management group. // It will deep merge the supplied assignments with the existing assignments. func (alzmg *HierarchyManagementGroup) ModifyPolicyAssignment( name string, parameters map[string]*armpolicy.ParameterValuesValue, enforcementMode *armpolicy.EnforcementMode, nonComplianceMessages []*armpolicy.NonComplianceMessage, identity *armpolicy.Identity, resourceSelectors []*armpolicy.ResourceSelector, overrides []*armpolicy.Override, ) error { if _, ok := alzmg.policyAssignments[name]; !ok { return fmt.Errorf("HierarchyManagementGroup.ModifyPolicyAssignment: policy assignment %s not found in management group %s", name, alzmg.id) } if alzmg.policyAssignments[name].Properties == nil { return fmt.Errorf("HierarchyManagementGroup.ModifyPolicyAssignment: properties for policy assignment %s in management group %s is nil", name, alzmg.id) } if alzmg.policyAssignments[name].Properties.Parameters == nil && len(parameters) > 0 { alzmg.policyAssignments[name].Properties.Parameters = make(map[string]*armpolicy.ParameterValuesValue, len(parameters)) } for k, v := range parameters { // Only add parameter if it exists in the referenced policy definition. ref, err := alzmg.policyAssignments[name].ReferencedPolicyDefinitionResourceId() if err != nil { return fmt.Errorf("HierarchyManagementGroup.ModifyPolicyAssignment: error getting referenced policy definition resource id for policy assignment %s: %w", name, err) } if !alzmg.hierarchy.alzlib.AssignmentReferencedDefinitionHasParameter(ref, k) { return fmt.Errorf("HierarchyManagementGroup.ModifyPolicyAssignment: parameter `%s` not found in referenced %s `%s` for policy assignment `%s`", k, ref.ResourceType.Type, ref.Name, name) } alzmg.policyAssignments[name].Properties.Parameters[k] = v } if enforcementMode != nil { alzmg.policyAssignments[name].Properties.EnforcementMode = enforcementMode } if nonComplianceMessages != nil { alzmg.policyAssignments[name].Properties.NonComplianceMessages = nonComplianceMessages } if resourceSelectors != nil { alzmg.policyAssignments[name].Properties.ResourceSelectors = resourceSelectors } if overrides != nil { alzmg.policyAssignments[name].Properties.Overrides = overrides } if identity != nil { alzmg.policyAssignments[name].Identity = identity } return nil } // parseArmFunctionInPolicySetParameter evaluates the ARM expression in a policy set parameter for a referenced definition. // It builds a map of the parameters in the policy set definition and the assignment and evaluates the ARM expression using goarmfunctions. func parseArmFunctionInPolicySetParameter(pdRef, paramName string, ass *armpolicy.Assignment, setDef *armpolicy.SetDefinition) (any, error) { resultantParams := make(map[string]any) for k, v := range setDef.Properties.Parameters { resultantParams[k] = v.DefaultValue } for k, v := range ass.Properties.Parameters { resultantParams[k] = v.Value } var toParse string for _, def := range setDef.Properties.PolicyDefinitions { if *def.PolicyDefinitionReferenceID != pdRef { continue } p, ok := def.Parameters[paramName] if !ok { return nil, fmt.Errorf("parseArmFunctionInPolicySetParameter: paramName %s not found in %s", paramName, *def.PolicyDefinitionReferenceID) } pStr, ok := p.Value.(string) if !ok { return nil, fmt.Errorf("parseArmFunctionInPolicySetParameter: paramName %s in %s is not a string", paramName, *def.PolicyDefinitionReferenceID) } toParse = pStr } res, err := goarmfunctions.LexAndParse(context.Background(), toParse, resultantParams, nil) if err != nil { return nil, fmt.Errorf("parseArmFunctionInPolicySetParameter: error parsing parameter %s in reference %s in set definition %s: %w", paramName, pdRef, *setDef.Name, err) } return res, nil } // updatePolicyDefinitions re-writes the policy definition resource IDs for the correct management group. func updatePolicyDefinitions(mg *HierarchyManagementGroup) { for k, v := range mg.policyDefinitions { v.ID = to.Ptr(fmt.Sprintf(PolicyDefinitionIdFmt, mg.id, k)) } } // These for loops re-write the referenced policy definition resource IDs // for all policy sets. // It looks up the policy definition names that are in all archetypes in the Deployment. // If it is found, the definition reference id is re-written with the correct management group name. // If it is not found, we assume that it's built-in. func updatePolicySetDefinitions(mg *HierarchyManagementGroup, pd2mg map[string]mapset.Set[string]) error { for psdName, psd := range mg.policySetDefinitions { psd.ID = to.Ptr(fmt.Sprintf(PolicySetDefinitionIdFmt, mg.id, psdName)) refs := psd.PolicyDefinitionReferences() if refs == nil { return fmt.Errorf("updatePolicySetDefinitions: error getting policy definition references for policy set definition %s", psdName) } for _, pdr := range refs { pdname, err := assets.NameFromResourceId(*pdr.PolicyDefinitionID) if err != nil { return fmt.Errorf("updatePolicySetDefinitions: error getting policy definition name from resource id %s: %w", *pdr.PolicyDefinitionID, err) } // if the referenced policy definition is custom, we need to update the reference if definitionMgs, ok := pd2mg[pdname]; ok { updated := false for definitionMg := range definitionMgs.Iter() { if definitionMg != mg.id && !mg.HasParent(definitionMg) { continue } pdr.PolicyDefinitionID = to.Ptr(fmt.Sprintf(PolicyDefinitionIdFmt, definitionMg, pdname)) updated = true break } if !updated { return fmt.Errorf("updatePolicySetDefinitions: policy set definition %s has a policy definition %s that is not in the same hierarchy", psdName, pdname) } } } } return nil } func updatePolicyAsignments(mg *HierarchyManagementGroup, pd2mg, psd2mg map[string]mapset.Set[string]) error { // Update resource ids and refs. for assignmentName, assignment := range mg.policyAssignments { assignment.ID = to.Ptr(fmt.Sprintf(PolicyAssignmentIdFmt, mg.id, assignmentName)) assignment.Properties.Scope = to.Ptr(fmt.Sprintf(ManagementGroupIdFmt, mg.id)) if assignment.Location != nil { assignment.Location = &mg.location } // rewrite the referenced policy definition id // if the policy definition is in the list. pdRes, err := assignment.ReferencedPolicyDefinitionResourceId() if err != nil { return fmt.Errorf("updatePolicyAssignments: error parsing policy definition id for policy assignment %s: %w", assignmentName, err) } switch strings.ToLower(pdRes.ResourceType.Type) { case "policydefinitions": if deploymentMgs, ok := pd2mg[pdRes.Name]; ok { updated := false for deploymentMg := range deploymentMgs.Iter() { if deploymentMg != mg.id && !mg.HasParent(deploymentMg) { continue } assignment.Properties.PolicyDefinitionID = to.Ptr(fmt.Sprintf(PolicyDefinitionIdFmt, deploymentMg, pdRes.Name)) updated = true break } if !updated { return fmt.Errorf("updatePolicyAssignments: policy assignment %s has a policy definition %s that is not in the same hierarchy", assignmentName, pdRes.Name) } } case "policysetdefinitions": if deploymentMg, ok := psd2mg[pdRes.Name]; ok { updated := false for deploymentMg := range deploymentMg.Iter() { if deploymentMg != mg.id && !mg.HasParent(deploymentMg) { continue } assignment.Properties.PolicyDefinitionID = to.Ptr(fmt.Sprintf(PolicySetDefinitionIdFmt, deploymentMg, pdRes.Name)) updated = true break } if !updated { return fmt.Errorf("updatePolicyAssignments: policy assignment %s has a policy set definition %s that is not in the same hierarchy", assignmentName, pdRes.Name) } } default: return fmt.Errorf("updatePolicyAssignments: policy assignment %s has invalid referenced definition/set resource type with id: %s", assignmentName, pdRes.Name) } } return nil } func updateRoleDefinitions(alzmg *HierarchyManagementGroup) { for _, roledef := range alzmg.roleDefinitions { u := uuidV5(alzmg.id, *roledef.Name) roledef.Name = to.Ptr(u.String()) roledef.ID = to.Ptr(fmt.Sprintf(RoleDefinitionIdFmt, alzmg.id, u)) roledef.Properties.RoleName = to.Ptr(fmt.Sprintf("%s (%s)", *roledef.Properties.RoleName, alzmg.id)) if len(roledef.Properties.AssignableScopes) == 0 { roledef.Properties.AssignableScopes = make([]*string, 1) } roledef.Properties.AssignableScopes[0] = to.Ptr(alzmg.ResourceId()) } } func newManagementGroup() *HierarchyManagementGroup { return &HierarchyManagementGroup{ policyRoleAssignments: mapset.NewThreadUnsafeSet[PolicyRoleAssignment](), policyDefinitions: make(map[string]*assets.PolicyDefinition), policySetDefinitions: make(map[string]*assets.PolicySetDefinition), policyAssignments: make(map[string]*assets.PolicyAssignment), roleDefinitions: make(map[string]*assets.RoleDefinition), } } // copyMap takes a map and returns a map with a deep copy of the values. func copyMap[E comparable, T any](m map[E]T) map[E]T { m2 := make(map[E]T, len(m)) for k, v := range m { m2[k] = deep.MustCopy(v) } return m2 } func (mg HierarchyManagementGroup) MarshalJSON() ([]byte, error) { type marshalHierarchyManagementGroup struct { Children []string `json:"children,omitempty"` // The ids of the children of the management group. DisplayName string `json:"display_name,omitempty"` // The display name of the management group. Exists bool `json:"exists,omitempty"` // Whether the management group already exists in the hierarchy. Id string `json:"id,omitempty"` // The name of the management group, forming the last part of the resource id. Level int `json:"level,omitempty"` // The level of the management group in the hierarchy. Location string `json:"location,omitempty"` // The default location to use for artifacts in the management group. Parent *string `json:"parent,omitempty"` // The id of the parent management group. PolicyAssignments map[string]*assets.PolicyAssignment `json:"policy_assignments,omitempty"` // The policy assignments in the management group. PolicyDefinitions map[string]*assets.PolicyDefinition `json:"policy_definitions,omitempty"` // The policy definitions in the management group. PolicyRoleAssignments []PolicyRoleAssignment `json:"policy_role_assignments,omitempty"` // The additional role assignments needed for the policy assignments. PolicySetDefinitions map[string]*assets.PolicySetDefinition `json:"policy_set_definitions,omitempty"` // The policy set definitions in the management group. RoleDefinitions map[string]*assets.RoleDefinition `json:"role_definitions,omitempty"` // The role definitions in the management group. } childrenIds := make([]string, mg.children.Cardinality()) for i, child := range mg.children.ToSlice() { childrenIds[i] = child.id } var parentId *string switch { case mg.parentExternal != nil: parentId = mg.parentExternal case mg.parent != nil: parentId = &mg.parent.id } tmp := marshalHierarchyManagementGroup{ Children: childrenIds, DisplayName: mg.displayName, Exists: mg.exists, Id: mg.id, Level: mg.level, Location: mg.location, Parent: parentId, PolicyAssignments: mg.policyAssignments, PolicyDefinitions: mg.policyDefinitions, PolicyRoleAssignments: mg.policyRoleAssignments.ToSlice(), PolicySetDefinitions: mg.policySetDefinitions, RoleDefinitions: mg.roleDefinitions, } return json.Marshal(tmp) }