alzlib.go (707 lines of code) (raw):

// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package alzlib import ( "context" "errors" "fmt" "slices" "strings" "sync" "github.com/Azure/alzlib/assets" "github.com/Azure/alzlib/internal/processor" "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" ) const ( defaultParallelism = 10 // default number of parallel requests to make to Azure APIs defaultOverwrite = false ) // AlzLib is the structure that gets built from the the library files // do not create this directly, use NewAlzLib instead. type AlzLib struct { Options *AlzLibOptions archetypes map[string]*Archetype architectures map[string]*Architecture policyAssignments map[string]*assets.PolicyAssignment policyDefinitions map[string]*assets.PolicyDefinition policySetDefinitions map[string]*assets.PolicySetDefinition roleDefinitions map[string]*assets.RoleDefinition defaultPolicyAssignmentValues DefaultPolicyAssignmentValues metadata []*Metadata clients *azureClients mu sync.RWMutex // mu is a mutex to concurrency protect the AlzLib maps } type azureClients struct { policyClient *armpolicy.ClientFactory } // AlzLibOptions are options for the AlzLib. type AlzLibOptions struct { AllowOverwrite bool // AllowOverwrite allows overwriting of existing policy assignments when processing additional libraries with AlzLib.Init(). Parallelism int // Parallelism is the number of parallel requests to make to Azure APIs when getting policy definitions and policy set definitions. } // NewAlzLib returns a new instance of the alzlib library, optionally using the supplied directory // for additional policy (set) definitions. // To customize the options for the AlzLib, pass in an AlzLibOptions struct, otherwise the default options will be used. func NewAlzLib(opts *AlzLibOptions) *AlzLib { if opts == nil { opts = defaultAlzLibOptions() } az := &AlzLib{ Options: opts, archetypes: make(map[string]*Archetype), architectures: make(map[string]*Architecture), policyAssignments: make(map[string]*assets.PolicyAssignment), policyDefinitions: make(map[string]*assets.PolicyDefinition), policySetDefinitions: make(map[string]*assets.PolicySetDefinition), roleDefinitions: make(map[string]*assets.RoleDefinition), metadata: make([]*Metadata, 0, 10), defaultPolicyAssignmentValues: make(DefaultPolicyAssignmentValues), clients: new(azureClients), mu: sync.RWMutex{}, } return az } func defaultAlzLibOptions() *AlzLibOptions { return &AlzLibOptions{ Parallelism: defaultParallelism, AllowOverwrite: defaultOverwrite, } } // Metadata returns all the registered metadata in the AlzLib struct. func (az *AlzLib) Metadata() []*Metadata { az.mu.RLock() defer az.mu.RUnlock() return deep.MustCopy(az.metadata) } // AddPolicyAssignments adds policy assignments to the AlzLib struct. func (az *AlzLib) AddPolicyAssignments(pas ...*assets.PolicyAssignment) error { az.mu.Lock() defer az.mu.Unlock() for _, pa := range pas { if pa == nil || pa.Name == nil || *pa.Name == "" { continue } if _, exists := az.policyAssignments[*pa.Name]; exists && !az.Options.AllowOverwrite { return fmt.Errorf("Alzlib.AddPolicyAssignments: policy assignment with name %s already exists and allow overwrite not set", *pa.Name) } cpy, err := deep.Copy(pa) if err != nil { return fmt.Errorf("Alzlib.AddPolicyAssignments: error making deep copy of policy assignment %s: %w", *pa.Name, err) } az.policyAssignments[*pa.Name] = cpy } return nil } // AddPolicyDefinitions adds policy definitions to the AlzLib struct. func (az *AlzLib) AddPolicyDefinitions(pds ...*assets.PolicyDefinition) error { az.mu.Lock() defer az.mu.Unlock() for _, pd := range pds { if pd == nil || pd.Name == nil || *pd.Name == "" { continue } if _, exists := az.policyDefinitions[*pd.Name]; exists && !az.Options.AllowOverwrite { return fmt.Errorf("Alzlib.AddPolicyAssignments: policy definition with name %s already exists and allow overwrite not set", *pd.Name) } cpy, err := deep.Copy(pd) if err != nil { return fmt.Errorf("Alzlib.AddPolicyAssignments: error making deep copy of policy definition %s: %w", *pd.Name, err) } az.policyDefinitions[*pd.Name] = cpy } return nil } // AddPolicySetDefinitions adds policy set definitions to the AlzLib struct. func (az *AlzLib) AddPolicySetDefinitions(psds ...*assets.PolicySetDefinition) error { az.mu.Lock() defer az.mu.Unlock() for _, psd := range psds { if psd == nil || psd.Name == nil || *psd.Name == "" { continue } if _, exists := az.policyDefinitions[*psd.Name]; exists && !az.Options.AllowOverwrite { return fmt.Errorf("Alzlib.AddPolicySetDefinitions: policy set definition with name %s already exists and allow overwrite not set", *psd.Name) } cpy, err := deep.Copy(psd) if err != nil { return fmt.Errorf("Alzlib.AddPolicySetDefinitions: error making deep copy of policy set definition %s: %w", *psd.Name, err) } az.policySetDefinitions[*psd.Name] = cpy } return nil } // AddRoleDefinitions adds role definitions to the AlzLib struct. func (az *AlzLib) AddRoleDefinitions(rds ...*assets.RoleDefinition) error { az.mu.Lock() defer az.mu.Unlock() for _, rd := range rds { if rd == nil || rd.Name == nil || *rd.Name == "" { continue } if _, exists := az.policyDefinitions[*rd.Name]; exists && !az.Options.AllowOverwrite { return fmt.Errorf("Alzlib.AddPolicyAssignments: role definition with name %s already exists and allow overwrite not set", *rd.Name) } cpy, err := deep.Copy(rd) if err != nil { return fmt.Errorf("Alzlib.AddPolicyAssignments: error making deep copy of role definition %s: %w", *rd.Name, err) } az.roleDefinitions[*rd.Name] = cpy } return nil } // PolicyAssignments returns a slice of all the policy assignment names in the library. func (az *AlzLib) PolicyAssignments() []string { az.mu.RLock() defer az.mu.RUnlock() result := make([]string, 0, len(az.policyAssignments)) for k := range az.policyAssignments { result = append(result, k) } slices.Sort(result) return result } // PolicyDefinitions returns a slice of all the policy definition names in the library. func (az *AlzLib) PolicyDefinitions() []string { az.mu.RLock() defer az.mu.RUnlock() result := make([]string, 0, len(az.policyDefinitions)) for k := range az.policyDefinitions { result = append(result, k) } slices.Sort(result) return result } // PolicySetDefinitions returns a slice of all the policy set definition names in the library. func (az *AlzLib) PolicySetDefinitions() []string { az.mu.RLock() defer az.mu.RUnlock() result := make([]string, 0, len(az.policySetDefinitions)) for k := range az.policySetDefinitions { result = append(result, k) } slices.Sort(result) return result } // RoleDefinitions returns a slice of all the role definition names in the library. func (az *AlzLib) RoleDefinitions() []string { az.mu.RLock() defer az.mu.RUnlock() result := make([]string, 0, len(az.roleDefinitions)) for k := range az.roleDefinitions { result = append(result, k) } slices.Sort(result) return result } // Archetypes returns a list of the archetypes in the AlzLib struct. func (az *AlzLib) Archetypes() []string { az.mu.RLock() defer az.mu.RUnlock() result := make([]string, 0, len(az.archetypes)) for k := range az.archetypes { result = append(result, k) } slices.Sort(result) return result } // Archetype returns a copy of the requested archetype by name. func (az *AlzLib) Archetype(name string) *Archetype { az.mu.RLock() defer az.mu.RUnlock() arch, ok := az.archetypes[name] if !ok { return nil } return arch.copy() } // Architectures returns a list of the architecture names in the AlzLib struct. func (az *AlzLib) Architectures() []string { az.mu.RLock() defer az.mu.RUnlock() result := make([]string, 0, len(az.architectures)) for k := range az.architectures { result = append(result, k) } slices.Sort(result) return result } func (az *AlzLib) PolicyDefaultValues() []string { az.mu.RLock() defer az.mu.RUnlock() result := make([]string, 0, len(az.defaultPolicyAssignmentValues)) for k := range az.defaultPolicyAssignmentValues { result = append(result, k) } slices.Sort(result) return result } func (az *AlzLib) PolicyDefaultValue(name string) *DefaultPolicyAssignmentValuesValue { az.mu.RLock() defer az.mu.RUnlock() val, ok := az.defaultPolicyAssignmentValues[name] if !ok { return nil } ret := val.copy() return &ret } // Architecture returns the requested architecture. func (az *AlzLib) Architecture(name string) *Architecture { az.mu.RLock() defer az.mu.RUnlock() arch, ok := az.architectures[name] if !ok { return nil } return arch } // PolicyDefinitionExists returns true if the policy definition name exists in the AlzLib struct. func (az *AlzLib) PolicyDefinitionExists(name string) bool { az.mu.RLock() defer az.mu.RUnlock() _, exists := az.policyDefinitions[name] return exists } // PolicySetDefinitionExists returns true if the policy set definition name exists in the AlzLib struct. func (az *AlzLib) PolicySetDefinitionExists(name string) bool { az.mu.RLock() defer az.mu.RUnlock() _, exists := az.policySetDefinitions[name] return exists } // PolicyAssignmentExists returns true if the policy assignment exists name in the AlzLib struct. func (az *AlzLib) PolicyAssignmentExists(name string) bool { az.mu.RLock() defer az.mu.RUnlock() _, exists := az.policyAssignments[name] return exists } // RoleDefinitionExists returns true if the role definition name exists in the AlzLib struct. func (az *AlzLib) RoleDefinitionExists(name string) bool { az.mu.RLock() defer az.mu.RUnlock() _, exists := az.roleDefinitions[name] return exists } // PolicyDefinition returns a deep copy of the requested policy definition. // This is safe to modify without affecting the original. func (az *AlzLib) PolicyDefinition(name string) *assets.PolicyDefinition { az.mu.RLock() defer az.mu.RUnlock() pd, ok := az.policyDefinitions[name] if !ok { return nil } return deep.MustCopy(pd) } // SetAssignPermissionsOnDefinitionParameter sets the AssignPermissions metadata field to true for the definition and parameter with the given name. func (az *AlzLib) SetAssignPermissionsOnDefinitionParameter(definitionName, parameterName string) { az.mu.Lock() defer az.mu.Unlock() definition, ok := az.policyDefinitions[definitionName] if !ok { return } definition.SetAssignPermissionsOnParameter(parameterName) } // UnsetAssignPermissionsOnDefinitionParameter removes the AssignPermissions metadata field to true for the definition and parameter with the given name. func (az *AlzLib) UnsetAssignPermissionsOnDefinitionParameter(definitionName, parameterName string) { az.mu.Lock() defer az.mu.Unlock() definition, ok := az.policyDefinitions[definitionName] if !ok { return } definition.UnsetAssignPermissionsOnParameter(parameterName) } // GetPolicySetDefinition returns a deep copy of the requested policy set definition. // This is safe to modify without affecting the original. func (az *AlzLib) PolicyAssignment(name string) *assets.PolicyAssignment { az.mu.RLock() defer az.mu.RUnlock() pa, ok := az.policyAssignments[name] if !ok { return nil } return deep.MustCopy(pa) } // PolicySetDefinition returns a deep copy of the requested policy set definition. // This is safe to modify without affecting the original. func (az *AlzLib) PolicySetDefinition(name string) *assets.PolicySetDefinition { az.mu.RLock() defer az.mu.RUnlock() psd, ok := az.policySetDefinitions[name] if !ok { return nil } return deep.MustCopy(psd) } // RoleDefinition returns a deep copy of the requested role definition. // This is safe to modify without affecting the original. func (az *AlzLib) RoleDefinition(name string) *assets.RoleDefinition { az.mu.RLock() defer az.mu.RUnlock() rd, ok := az.roleDefinitions[name] if !ok { return nil } return deep.MustCopy(rd) } // AddPolicyClient adds an authenticated *armpolicy.ClientFactory to the AlzLib struct. // This is needed to get policy objects from Azure. func (az *AlzLib) AddPolicyClient(client *armpolicy.ClientFactory) { az.mu.Lock() defer az.mu.Unlock() az.clients.policyClient = client } // Init processes ALZ libraries, supplied as `LibraryReference` interfaces. // Use FetchAzureLandingZonesLibraryMember/FetchLibraryByGetterString to get the library from GitHub. // It populates the struct with the results of the processing. func (az *AlzLib) Init(ctx context.Context, libs ...LibraryReference) error { az.mu.Lock() defer az.mu.Unlock() if az.Options == nil || az.Options.Parallelism == 0 { return errors.New("Alzlib.Init: alzlib Options not set or parallelism is `0`") } // Process the libraries for _, ref := range libs { if ref == nil { return errors.New("Alzlib.Init: library is nil") } if ref.FS() == nil { if _, err := ref.Fetch(ctx, hash(ref)); err != nil { return fmt.Errorf("Alzlib.Init: error fetching library %s: %w", ref, err) } } res := processor.NewResult() pc := processor.NewProcessorClient(ref.FS()) if err := pc.Process(res); err != nil { return fmt.Errorf("Alzlib.Init: error processing library %v: %w", ref, err) } if res.Metadata != nil { az.metadata = append(az.metadata, NewMetadata(res.Metadata, ref)) } // Put results into the AlzLib. if err := az.addPolicyAndRoleAssets(res); err != nil { return fmt.Errorf("Alzlib.Init: error adding processed result to AlzLib: %w", err) } // Add default policy values if err := az.addDefaultPolicyAssignmentValues(res); err != nil { return fmt.Errorf("Alzlib.Init: error adding default policy assignment values to AlzLib: %w", err) } // Generate archetypes if err := az.generateArchetypes(res); err != nil { return fmt.Errorf("Alzlib.Init: error generating archetypes: %w", err) } // Generate override archetypes if err := az.generateOverrideArchetypes(res); err != nil { return fmt.Errorf("Alzlib.Init: error generating override archetypes: %w", err) } // Generate architectures if err := az.generateArchitectures(res); err != nil { return fmt.Errorf("Alzlib.Init: error generating architectures: %w", err) } } return nil } // GetDefinitionsFromAzure takes a slice of strings of Azure resource IDs of policy definitions and policy set definitions. // It then fetches them from Azure if they don't already exist (determined by last segment tof resource id). // For set definitions we need to get all of them, even if they exist in AlzLib already because they can contain built-in definitions. func (az *AlzLib) GetDefinitionsFromAzure(ctx context.Context, pds []string) error { policyDefsToGet := mapset.NewThreadUnsafeSet[string]() policySetDefsToGet := mapset.NewThreadUnsafeSet[string]() for _, pd := range pds { resId, err := arm.ParseResourceID(pd) if err != nil { return fmt.Errorf("Alzlib.GetDefinitionsFromAzure: error parsing resource ID %s: %w", pd, err) } switch strings.ToLower(resId.ResourceType.Type) { case "policydefinitions": if !az.PolicyDefinitionExists(resId.Name) { policyDefsToGet.Add(resId.Name) } case "policysetdefinitions": // If the set is not present, OR if the set contains referenced definitions that are not present // add it to the list of set defs to get. exists := az.PolicySetDefinitionExists(resId.Name) if exists { psd := az.PolicySetDefinition(resId.Name) if psd == nil { return fmt.Errorf("Alzlib.GetDefinitionsFromAzure: error getting policy set definition %s: %w", pd, err) } pdrefs := psd.PolicyDefinitionReferences() if pdrefs == nil { return fmt.Errorf("Alzlib.GetDefinitionsFromAzure: error getting policy definition references for policy set definition %s: %w", pd, err) } for _, ref := range pdrefs { subResId, err := arm.ParseResourceID(*ref.PolicyDefinitionID) if err != nil { return fmt.Errorf("Alzlib.GetDefinitionsFromAzure: policy set definition %s error parsing referenced definition resource id: %w", *psd.Name, err) } if _, exists := az.policyDefinitions[subResId.Name]; !exists { policyDefsToGet.Add(subResId.Name) } } } else { policySetDefsToGet.Add(resId.Name) } default: return fmt.Errorf("Alzlib.GetDefinitionsFromAzure: unexpected policy definition type when processing assignments: %s", pd) } } // Add the referenced built-in definitions and set definitions to the AlzLib struct // so that we can use the data to determine the correct role assignments at scope. if policyDefsToGet.Cardinality() != 0 { if err := az.getBuiltInPolicies(ctx, policyDefsToGet.ToSlice()); err != nil { return fmt.Errorf("Alzlib.GetDefinitionsFromAzure: error getting built-in policy definitions: %w", err) } } if policySetDefsToGet.Cardinality() != 0 { if err := az.getBuiltInPolicySets(ctx, policySetDefsToGet.ToSlice()); err != nil { return fmt.Errorf("Alzlib.GetDefinitionsFromAzure: error getting built-in policy set definitions: %w", err) } } return nil } // AssignmentReferencedDefinitionHasParameter checks if the referenced definition of an assignment has a specific parameter. // It takes a resource ID and a parameter name as input and returns a boolean indicating whether the parameter exists or not. func (az *AlzLib) AssignmentReferencedDefinitionHasParameter(res *arm.ResourceID, param string) bool { switch strings.ToLower(res.ResourceType.Type) { case "policydefinitions": pd := az.PolicyDefinition(res.Name) if pd == nil { return false } if pd.Parameter(param) != nil { return true } case "policysetdefinitions": psd := az.PolicySetDefinition(res.Name) if psd == nil { return false } if psd.Parameter(param) != nil { return true } } return false } // getBuiltInPolicies retrieves the built-in policy definitions with the given names // and adds them to the AlzLib struct. func (az *AlzLib) getBuiltInPolicies(ctx context.Context, names []string) error { if az.clients.policyClient == nil { return errors.New("Alzlib.getBuiltInPolicies: policy client not set") } pdclient := az.clients.policyClient.NewDefinitionsClient() for _, name := range names { if az.PolicyDefinitionExists(name) { continue } resp, err := pdclient.GetBuiltIn(ctx, name, nil) if err != nil { return fmt.Errorf("Alzlib.getBuiltInPolicies: error getting built-in policy definition %s: %w", name, err) } if err := az.AddPolicyDefinitions(assets.NewPolicyDefinition(resp.Definition)); err != nil { return fmt.Errorf("Alzlib.getBuiltInPolicies: error adding built-in policy definition %s: %w", name, err) } } return nil } // getBuiltInPolicySets retrieves the built-in policy set definitions with the given names // and adds them to the AlzLib struct. func (az *AlzLib) getBuiltInPolicySets(ctx context.Context, names []string) error { if az.clients.policyClient == nil { return errors.New("Alzlib.getBuiltInPolicySets: policy client not set") } // We need to keep track of the names we've processed // so that we can get the policy definitions referenced within them. processedNames := make([]string, 0, len(names)) psclient := az.clients.policyClient.NewSetDefinitionsClient() for _, name := range names { if az.PolicySetDefinitionExists(name) { continue } resp, err := psclient.GetBuiltIn(ctx, name, nil) if err != nil { return fmt.Errorf("Alzlib.getBuiltInPolicySets: error getting built-in policy set definition %s: %w", name, err) } // Add set definition to the AlzLib. if err := az.AddPolicySetDefinitions(assets.NewPolicySetDefinition(resp.SetDefinition)); err != nil { return fmt.Errorf("Alzlib.getBuiltInPolicySets: error adding built-in policy set definition %s: %w", name, err) } processedNames = append(processedNames, name) } // Get the policy definitions for newly added policy set definitions. defnames := make([]string, 0) for _, name := range processedNames { def := az.PolicySetDefinition(name) refs := def.PolicyDefinitionReferences() if refs == nil { return fmt.Errorf("Alzlib.getBuiltInPolicySets: error getting policy definition references for policy set definition `%s`. Either the policy set definition does not exist or cannot get policy definition references", name) } for _, ref := range refs { resId, err := arm.ParseResourceID(*ref.PolicyDefinitionID) if err != nil { if ref.PolicyDefinitionID == nil { return fmt.Errorf("Alzlib.getBuiltInPolicySets: error getting policy definition references for policy set definition `%s`: policy definition ID is nil", name) } return fmt.Errorf("Alzlib.getBuiltInPolicySets: error parsing resource id `%s` referenced in policy set `%s`", *ref.PolicyDefinitionID, name) } defnames = append(defnames, resId.Name) } } if err := az.getBuiltInPolicies(ctx, defnames); err != nil { return fmt.Errorf("Alzlib.getBuiltInPolicySets: error getting new built-in policy definitions referenced by policy sets: %w", err) } return nil } // addPolicyAndRoleAssets adds the results of a processed library to the AlzLib. func (az *AlzLib) addPolicyAndRoleAssets(res *processor.Result) error { for k, v := range res.PolicyDefinitions { if _, exists := az.policyDefinitions[k]; exists && !az.Options.AllowOverwrite { return fmt.Errorf("Alzlib.addProcessedResult: policy definition %s already exists in the library", k) } az.policyDefinitions[k] = assets.NewPolicyDefinition(*v) } for k, v := range res.PolicySetDefinitions { if _, exists := az.policySetDefinitions[k]; exists && !az.Options.AllowOverwrite { return fmt.Errorf("Alzlib.addProcessedResult: policy definition %s already exists in the library", k) } az.policySetDefinitions[k] = assets.NewPolicySetDefinition(*v) } for k, v := range res.PolicyAssignments { if _, exists := az.policyAssignments[k]; exists && !az.Options.AllowOverwrite { return fmt.Errorf("Alzlib.addProcessedResult: policy assignment %s already exists in the library", k) } az.policyAssignments[k] = assets.NewPolicyAssignment(*v) } for k, v := range res.RoleDefinitions { if _, exists := az.roleDefinitions[k]; exists && !az.Options.AllowOverwrite { return fmt.Errorf("Alzlib.addProcessedResult: role definition %s already exists in the library", k) } az.roleDefinitions[k] = assets.NewRoleDefinition(*v) } return nil } func (az *AlzLib) addDefaultPolicyAssignmentValues(res *processor.Result) error { for defName, def := range res.LibDefaultPolicyValues { if _, exists := az.defaultPolicyAssignmentValues[defName]; exists { if !az.Options.AllowOverwrite { return fmt.Errorf("Alzlib.addDefaultPolicyValues: default name %s already exists in the defaults", defName) } delete(az.defaultPolicyAssignmentValues, defName) } for _, assignment := range def.PolicyAssignments { for _, param := range assignment.ParameterNames { if az.defaultPolicyAssignmentValues.AssignmentParameterComboExists(assignment.PolicyAssignmentName, param) { return fmt.Errorf("Alzlib.addDefaultPolicyValues: error processing default policy values for default name: `%s`, assignment `%s` and parameter `%s` already exists in defaults", defName, assignment.PolicyAssignmentName, param) } } az.defaultPolicyAssignmentValues.Add(defName, assignment.PolicyAssignmentName, def.Description, assignment.ParameterNames...) } } return nil } // generateArchetypes generates the archetypes from the result of the processor. // The archetypes are stored in the AlzLib instance. func (az *AlzLib) generateArchetypes(res *processor.Result) error { // add empty archetype if it doesn't exist. if _, exists := az.archetypes["empty"]; !exists { if _, exists := res.LibArchetypes["empty"]; !exists { res.LibArchetypes["empty"] = &processor.LibArchetype{ Name: "empty", PolicyAssignments: mapset.NewThreadUnsafeSet[string](), PolicyDefinitions: mapset.NewThreadUnsafeSet[string](), PolicySetDefinitions: mapset.NewThreadUnsafeSet[string](), RoleDefinitions: mapset.NewThreadUnsafeSet[string](), } } } // generate alzlib archetypes. for k, v := range res.LibArchetypes { if _, exists := az.archetypes[k]; exists && !az.Options.AllowOverwrite { return fmt.Errorf("Alzlib.generateArchetypes: archetype %s already exists in the library", v.Name) } arch := NewArchetype(v.Name) for pd := range v.PolicyDefinitions.Iter() { if _, ok := az.policyDefinitions[pd]; !ok { return fmt.Errorf("Alzlib.generateArchetypes: error processing archetype %s, policy definition %s does not exist in the library", k, pd) } arch.PolicyDefinitions.Add(pd) } for psd := range v.PolicySetDefinitions.Iter() { if _, ok := az.policySetDefinitions[psd]; !ok { return fmt.Errorf("Alzlib.generateArchetypes: error processing archetype %s, policy set definition %s does not exist in the library", k, psd) } arch.PolicySetDefinitions.Add(psd) } for pa := range v.PolicyAssignments.Iter() { if _, ok := az.policyAssignments[pa]; !ok { return fmt.Errorf("Alzlib.generateArchetypes: error processing archetype %s, policy assignment %s does not exist in the library", k, pa) } arch.PolicyAssignments.Add(pa) } for rd := range v.RoleDefinitions.Iter() { if _, ok := az.roleDefinitions[rd]; !ok { return fmt.Errorf("Alzlib.generateArchetypes: error processing archetype %s, role definition %s does not exist in the library", k, rd) } arch.RoleDefinitions.Add(rd) } az.archetypes[v.Name] = arch } return nil } // generateOverrideArchetypes generates the override archetypes from the result of the processor. // THis must be run after generateArchetypes. func (az *AlzLib) generateOverrideArchetypes(res *processor.Result) error { for name, ovr := range res.LibArchetypeOverrides { if _, exists := az.archetypes[name]; exists { return fmt.Errorf("Alzlib.generateOverrideArchetypes: error processing override archetype `%s` - it already exists in the library", name) } base, exists := az.archetypes[ovr.BaseArchetype] if !exists { return fmt.Errorf("Alzlib.generateOverrideArchetypes: error processing override archetype `%s` - base archetype `%s` does not exist in the library", name, ovr.BaseArchetype) } for pa := range ovr.PolicyAssignmentsToAdd.Iter() { if _, ok := az.policyAssignments[pa]; !ok { return fmt.Errorf("Alzlib.generateOverrideArchetypes: error processing override archetype `%s`, policy assignment `%s` does not exist in the library", name, pa) } } for pa := range ovr.PolicyAssignmentsToRemove.Iter() { if _, ok := az.policyAssignments[pa]; !ok { return fmt.Errorf("Alzlib.generateOverrideArchetypes: error processing override archetype `%s`, policy assignment `%s` does not exist in the library", name, pa) } } for pd := range ovr.PolicyDefinitionsToAdd.Iter() { if _, ok := az.policyDefinitions[pd]; !ok { return fmt.Errorf("Alzlib.generateOverrideArchetypes: error processing override archetype `%s`, policy definition `%s` does not exist in the library", name, pd) } } for pd := range ovr.PolicyDefinitionsToRemove.Iter() { if _, ok := az.policyDefinitions[pd]; !ok { return fmt.Errorf("Alzlib.generateOverrideArchetypes: error processing override archetype `%s`, policy definition `%s` does not exist in the library", name, pd) } } for psd := range ovr.PolicySetDefinitionsToAdd.Iter() { if _, ok := az.policySetDefinitions[psd]; !ok { return fmt.Errorf("Alzlib.generateOverrideArchetypes: error processing override archetype `%s`, policy set definition `%s` does not exist in the library", name, psd) } } for psd := range ovr.PolicySetDefinitionsToRemove.Iter() { if _, ok := az.policySetDefinitions[psd]; !ok { return fmt.Errorf("Alzlib.generateOverrideArchetypes: error processing override archetype `%s`, policy set definition `%s` does not exist in the library", name, psd) } } for rd := range ovr.RoleDefinitionsToAdd.Iter() { if _, ok := az.roleDefinitions[rd]; !ok { return fmt.Errorf("Alzlib.generateOverrideArchetypes: error processing override archetype `%s`, role definition `%s` does not exist in the library", name, rd) } } for rd := range ovr.RoleDefinitionsToRemove.Iter() { if _, ok := az.roleDefinitions[rd]; !ok { return fmt.Errorf("Alzlib.generateOverrideArchetypes: error processing override archetype `%s`, role definition `%s` does not exist in the library", name, rd) } } newArch := &Archetype{ PolicyDefinitions: base.PolicyDefinitions.Clone().Union(ovr.PolicyDefinitionsToAdd).Difference(ovr.PolicyDefinitionsToRemove), PolicySetDefinitions: base.PolicySetDefinitions.Clone().Union(ovr.PolicySetDefinitionsToAdd).Difference(ovr.PolicySetDefinitionsToRemove), PolicyAssignments: base.PolicyAssignments.Clone().Union(ovr.PolicyAssignmentsToAdd).Difference(ovr.PolicyAssignmentsToRemove), RoleDefinitions: base.RoleDefinitions.Clone().Union(ovr.RoleDefinitionsToAdd).Difference(ovr.RoleDefinitionsToRemove), name: name, } az.archetypes[name] = newArch } return nil } func (az *AlzLib) generateArchitectures(res *processor.Result) error { for name, libArch := range res.LibArchitectures { if _, exists := az.architectures[name]; exists && !az.Options.AllowOverwrite { return fmt.Errorf("Alzlib.generateArchitectures: error processing architecture %s - it already exists in the library", name) } validParents := mapset.NewThreadUnsafeSet[string]() for _, mg := range libArch.ManagementGroups { validParents.Add(mg.Id) } for _, mg := range libArch.ManagementGroups { if mg.ParentId != nil && !validParents.Contains(*mg.ParentId) { return fmt.Errorf("Alzlib.generateArchitectures: error processing architecture %s - management group %s has invalid parent %s", name, mg.Id, *mg.ParentId) } } arch := NewArchitecture(name, az) if err := architectureRecursion(nil, libArch, arch, az, 0); err != nil { return fmt.Errorf("Alzlib.generateArchitectures: error processing architecture %s: %w", name, err) } az.architectures[name] = arch } return nil } // architectureRecursion is a recursive function to build the architecture from the definitions read by the processor. func architectureRecursion(parents mapset.Set[string], libArch *processor.LibArchitecture, arch *Architecture, az *AlzLib, depth int) error { if depth > 5 { return errors.New("architectureRecursion: recursion depth exceeded") } newParents := mapset.NewThreadUnsafeSet[string]() if len(libArch.ManagementGroups) == 0 { return errors.New("architectureRecursion: no management groups found") } for _, mg := range libArch.ManagementGroups { switch { case depth == 0 && mg.ParentId == nil: if err := arch.addMgFromProcessor(mg, az); err != nil { return fmt.Errorf("architectureRecursion: error adding management group %s: %w", mg.Id, err) } case depth > 0 && mg.ParentId != nil: if parents == nil { return errors.New("architectureRecursion: depth > 1 and parents set to nil") } if !parents.Contains(*mg.ParentId) { continue } if !arch.mgs[*mg.ParentId].exists && mg.Exists { return fmt.Errorf("architectureRecursion: error adding management group %s, which is configured as existing but the parent management group %s does not exist and would be created", mg.Id, *mg.ParentId) } if err := arch.addMgFromProcessor(mg, az); err != nil { return fmt.Errorf("architectureRecursion: error adding management group %s: %w", mg.Id, err) } default: continue } newParents.Add(mg.Id) } if newParents.Cardinality() > 0 { return architectureRecursion(newParents, libArch, arch, az, depth+1) } return nil }