cmd/hub/manifest/parse.go (210 lines of code) (raw):

// Copyright (c) 2022 EPAM Systems, Inc. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package manifest import ( "bytes" _ "embed" "fmt" "log" "path/filepath" "strings" "gopkg.in/yaml.v2" "github.com/epam/hubctl/cmd/hub/config" "github.com/epam/hubctl/cmd/hub/storage" "github.com/epam/hubctl/cmd/hub/util" ) //go:embed hub-well-known-parameters.yaml var hubWellKnownParameters []byte func ParseManifest(manifestFilenames []string) (*Manifest, []Manifest, string, error) { yamlBytes, manifestFilename, err := storage.CheckAndRead(manifestFilenames, "manifest") if err != nil { return nil, nil, manifestFilename, fmt.Errorf("Unable to read %v manifest: %v", manifestFilenames, err) } yamlDocuments := bytes.Split(yamlBytes, []byte("\n---\n")) var manifests []Manifest for i, yamlDocument := range yamlDocuments { if len(yamlDocument) == 0 { continue } validateManifest(fmt.Sprintf("%s[%d]", manifestFilename, i), yamlDocument) var manifest Manifest err = yaml.Unmarshal(yamlDocument, &manifest) if err != nil { return nil, nil, manifestFilename, fmt.Errorf("Unable to parse %s (doc %d/%d): %v", manifestFilename, i+1, len(yamlDocuments), err) } manifest.Document = string(yamlDocument) manifests = append(manifests, manifest) } if len(manifests) == 0 { return nil, nil, manifestFilename, fmt.Errorf("No YAML documents in %s", manifestFilename) } else if len(manifests) > 1 { return &manifests[0], manifests[1:], manifestFilename, nil } else { return &manifests[0], nil, manifestFilename, nil } } func ParseParametersManifest(manifestFilenames []string) (*ParametersManifest, string, error) { yamlBytes, manifestFilename, err := storage.CheckAndRead(manifestFilenames, "parameters") if err != nil { return nil, manifestFilename, fmt.Errorf("Unable to read %v: %v", manifestFilenames, err) } yamlDocuments := bytes.Split(yamlBytes, []byte("\n---\n")) for i, yamlDocument := range yamlDocuments { if len(yamlDocument) == 0 { continue } validateManifest(manifestFilename, yamlDocument) var manifest ParametersManifest err = yaml.Unmarshal(yamlDocument, &manifest) if err != nil { return nil, manifestFilename, fmt.Errorf("Unable to parse %s: %v", manifestFilename, err) } if len(yamlDocuments) > i+1 { util.Warn("Parameters manifest `%s` contains more than one YAML document, only first is used", manifestFilename) } if len(manifest.Parameters) == 0 && config.Verbose { log.Printf("Parameters manifest `%s` contains no parameters", manifestFilename) } return &manifest, manifestFilename, nil } return nil, manifestFilename, fmt.Errorf("No YAML documents found in %s", manifestFilename) } func GetWellKnownParametersManifest() (*WellKnownParametersManifest, error) { yamlDocuments := bytes.Split(hubWellKnownParameters, []byte("\n---\n")) for i, yamlDocument := range yamlDocuments { if len(yamlDocument) == 0 { continue } var manifest WellKnownParametersManifest err := yaml.Unmarshal(yamlDocument, &manifest) if err != nil { return nil, fmt.Errorf("Unable to parse well-known parameters: %v", err) } if len(yamlDocuments) > i+1 { util.Warn("Embedded well-known parameters manifest contains more than one YAML document, only first is used") } return &manifest, nil } return nil, fmt.Errorf("No YAML documents found in embedded well-known parameters manifest") } func ParseComponentsManifests(components []ComponentRef, stackBaseDir string, componentsBaseDir string) ([]Manifest, error) { manifests := make([]Manifest, 0, len(components)) for _, component := range components { dir := ComponentSourceDirFromRef(&component, stackBaseDir, componentsBaseDir) filename := filepath.Join(dir, "hub-component.yaml") manifest, rest, _, err := ParseManifest([]string{filename}) if err != nil { return nil, fmt.Errorf("Unable to parse component `%s` manifest: %v", ComponentQualifiedNameFromRef(&component), err) } if len(rest) > 0 { util.Warn("Component `%s` manifest `%s` contains more than one YAML document, only first is used", ComponentQualifiedNameFromRef(&component), filename) } if len(manifest.Parameters) == 0 && config.Verbose { log.Printf("Component manifest `%s` contains no parameters", filename) } if manifest.Meta.Origin == "" { manifest.Meta.Origin = manifest.Meta.Name } if manifest.Meta.Kind == "" { manifest.Meta.Kind = manifest.Meta.Name } manifest.Meta.Name = component.Name manifests = append(manifests, *manifest) } return manifests, nil } func ParseComponentsManifestsWithExclusion(components []ComponentRef, excludedComponents []string, stackBaseDir string, componentsBaseDir string) ([]Manifest, error) { if config.Debug && len(excludedComponents) > 0 { log.Printf("Components excluded from parsing (in parent stack):\n\t%s", strings.Join(excludedComponents, ", ")) } filtered := make([]ComponentRef, 0, len(components)) for _, ref := range components { fqName := ComponentQualifiedNameFromRef(&ref) found := false for _, excluded := range excludedComponents { if fqName == excluded { found = true break } } if !found { filtered = append(filtered, ref) } } return ParseComponentsManifests(filtered, stackBaseDir, componentsBaseDir) } func GenerateLifecycleOrder(manifest *Manifest) ([]string, error) { componentsOrder := manifest.Lifecycle.Order notDefinedComponents := make([]string, 0) if len(componentsOrder) == 0 { componentsMap := make(map[string]ComponentRef) for _, ref := range manifest.Components { componentsMap[ref.Name] = ref } componentsOrder = make([]string, 0) for _, ref := range manifest.Components { if util.Contains(componentsOrder, ref.Name) { continue } dependencies, notDefinedDeps, err := resolveComponentDependencies(ref, componentsMap, "") if err != nil { return nil, err } for _, dependency := range dependencies { if !util.Contains(componentsOrder, dependency) { componentsOrder = append(componentsOrder, dependency) } } notDefinedComponents = appendUniq(notDefinedComponents, notDefinedDeps) componentsOrder = append(componentsOrder, ref.Name) } } if len(notDefinedComponents) > 0 { return nil, fmt.Errorf("These components are not defined but used as dependencies: %s", strings.Join(notDefinedComponents, ", ")) } return componentsOrder, nil } func resolveComponentDependencies(ref ComponentRef, componentsMap map[string]ComponentRef, prevComponentName string) ([]string, []string, error) { componentDependensies := ref.Depends if len(componentDependensies) == 0 { return componentDependensies, nil, nil } if util.Contains(componentDependensies, ref.Name) || util.Contains(componentDependensies, prevComponentName) { return nil, nil, fmt.Errorf("Components dependency cycle detected in definition of \"%s\" component", ref.Name) } result := make([]string, 0) notDefined := make([]string, 0) for _, dependencyName := range componentDependensies { if util.Contains(result, dependencyName) { continue } dependencyRef, exists := componentsMap[dependencyName] if exists { dependenciesChain, notDefinedChain, err := resolveComponentDependencies(dependencyRef, componentsMap, ref.Name) if err != nil { return nil, nil, err } result = appendUniq(result, dependenciesChain) notDefined = appendUniq(notDefined, notDefinedChain) result = append(result, dependencyName) } else { if !util.Contains(notDefined, dependencyName) { notDefined = append(notDefined, dependencyName) } } } return result, notDefined, nil } func appendUniq(result []string, items []string) []string { for _, item := range items { if !util.Contains(result, item) { result = append(result, item) } } return result }