cli/launchpad/folder.go (130 lines of code) (raw):

package launchpad import ( "fmt" "io" "log" "sort" "strings" ) const ( folderNameMin = 3 folderNameMax = 30 ) // folderSpecYAML defines GCP Folder's spec. type folderSpecYAML struct { // Inner mappings Id string `yaml:"id"` DisplayName string `yaml:"displayName"` ParentRef referenceYAML `yaml:"parentRef"` SubFolderSpecs []*folderSpecYAML `yaml:"folders"` Undefined map[string]interface{} `yaml:",inline"` // Catch-all for unintended behavior } // folders represents a list of folders. type folders []*folderYAML // add appends a folder into the folder list if it does not exist already. // // add returns existing folder's reference if attempted to add folder of the same ID. func (fs *folders) add(newF *folderYAML) *folderYAML { for _, f := range *fs { if f.Spec.Id == newF.Spec.Id { return f } } *fs = append(*fs, newF) return nil } // merge adds given list of folders into the current folders list recursively. // // merge will be called on subfolder when it exist in both current and given list of folders. func (fs *folders) merge(nfs *folders) { for _, f := range *nfs { if existF := fs.add(f); existF != nil { existF.subFolders.merge(&f.subFolders) // recursively merge sub folders } } } func (fs folders) sortedCopy() folders { buff := make(folders, len(fs)) // sorted subFolders copy(buff, fs) sort.SliceStable(buff, func(i, j int) bool { return buff[i].Spec.Id < buff[j].Spec.Id }) return buff } // folderYAML is a GCP Folder YAML representation. type folderYAML struct { headerYAML `yaml:",inline"` Spec folderSpecYAML `yaml:"spec"` subFolders folders // subFolders is a validated sub directories. } // resId returns an internal referencable id. func (f *folderYAML) resId() string { return fmt.Sprintf("%s.%s", Folder, f.Spec.Id) } // validate ensures input YAML fields are correct. // // validate also populates subFolders. func (f *folderYAML) validate() error { if f.Spec.Id == "" { return errMissingRequiredField } switch f.Spec.ParentRef.TargetTypeStr { // Validate Supported Parents case Organization.String(), Folder.String(): default: log.Printf("fatal: unsupported parent '%s' type for Folder\n", f.Spec.ParentRef.TargetTypeStr) return errInvalidParent } if !tfNameRegex.MatchString(f.Spec.Id) { log.Printf("GCP Folder [%s] ID does not conform to Terraform standard", f.Spec.Id) return errValidationFailed } if len(f.Spec.DisplayName) < folderNameMin || len(f.Spec.DisplayName) > folderNameMax { log.Printf("GCP Folder Name [%s] needs to be between %d and %d", f.Spec.DisplayName, folderNameMin, folderNameMax) return errValidationFailed } f.subFolders = newSubFoldersBySpecs(f.Spec.SubFolderSpecs, Folder, f.Spec.Id) for _, f := range f.subFolders { // triggers subfolder validation to validate and add nested folders if err := f.validate(); err != nil { return err } } return nil } // addToOrg adds the folder into the assembled organization. // // addToOrg also recursively add folder's subFolders into the org. func (f *folderYAML) addToOrg(ao *assembledOrg) error { if err := ao.registerResource(f, &f.Spec.ParentRef); err != nil { return err } for _, sf := range f.subFolders { // Recursively enroll sub-folders if err := sf.addToOrg(ao); err != nil { return err } } return nil } // resolveReferences processes references to folder. // // resolveReferences takes reference from folder as a subFolder of this folder. func (f *folderYAML) resolveReferences(refs []resourceHandler) error { for _, ref := range refs { switch r := ref.(type) { case *folderYAML: if f.Spec.Id != r.Spec.ParentRef.TargetId { // caller should already ensure this once log.Printf("fatail: mismatch parent id %s %s", f.resId(), r.Spec.ParentRef.resId()) return errInvalidParent } _ = f.subFolders.add(r) // silently ignore existing resource default: log.Printf("fatal: invalid %s parent for %s\n", f.resId(), r.resId()) return errInvalidInput } } return nil } // dump writes resource's string representation into provided buffer. func (f *folderYAML) dump(ind int, buff io.Writer) error { indent := strings.Repeat(" ", ind) _, err := fmt.Fprintf(buff, "%s+ %s.%s (\"%s\") < %s.%s\n", indent, Folder, f.Spec.Id, f.Spec.DisplayName, f.Spec.ParentRef.TargetTypeStr, f.Spec.ParentRef.TargetId) if err != nil { return err } for _, sf := range f.subFolders.sortedCopy() { err = sf.dump(ind+defaultIndentSize, buff) if err != nil { return err } } return nil } // newSubFoldersBySpecs initializes folderSpecYAMLs and turn it into a folderYAMLs. // // newSubFoldersBySpecs overwrites folderSpecYAML's parent field if parentId is provided. func newSubFoldersBySpecs(sfYAMLs []*folderSpecYAML, parentType crdKind, parentId string) []*folderYAML { var sfs []*folderYAML for _, sfYAML := range sfYAMLs { sf := folderYAML{Spec: *sfYAML} if parentId != "" { // overwrite parents setting sf.Spec.ParentRef.TargetTypeStr = parentType.String() sf.Spec.ParentRef.TargetId = parentId } sf.APIVersion = apiCFTv1alpha1 sf.KindStr = Folder.String() sfs = append(sfs, &sf) } return sfs }