cmd/hub/lifecycle/check.go (188 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 lifecycle import ( "fmt" "log" "os" "sort" "strings" "github.com/epam/hubctl/cmd/hub/config" "github.com/epam/hubctl/cmd/hub/manifest" "github.com/epam/hubctl/cmd/hub/parameters" "github.com/epam/hubctl/cmd/hub/state" "github.com/epam/hubctl/cmd/hub/util" ) // check there is a perfect match between components in Stack manifest and accompanied Components manifests func checkComponentsManifests(components []manifest.ComponentRef, componentsManifests []manifest.Manifest) { refs := manifest.ComponentsNamesFromRefs(components) docs := manifest.ComponentsNamesFromManifests(componentsManifests) sort.Strings(refs) sort.Strings(docs) if !util.Equal(refs, docs) { log.Fatalf("Components in stack manifest: %s;\n\tdoes not match components manifest documents: %s\n\t%s", strings.Join(refs, ", "), strings.Join(docs, ", "), "(do you have YAML file(s) with \\r\\n line endings?)") } } // check sources is an accessible local directory func checkComponentsSourcesExist(order []string, components []manifest.ComponentRef, stackBaseDir, componentsBaseDir string, skip func(int, string) bool) { for i, name := range order { if skip != nil && skip(i, name) { continue } component := manifest.ComponentRefByName(components, name) compName := manifest.ComponentQualifiedNameFromRef(component) dir := manifest.ComponentSourceDirFromRef(component, stackBaseDir, componentsBaseDir) info, err := os.Stat(dir) if err != nil { log.Fatalf("`%s` source directory for component `%s` not found: %v", dir, compName, err) } if !info.IsDir() { log.Fatalf("`%s` source for component `%s` is not a directory", dir, compName) } } } // check manifest.Lifecycle verbs func checkLifecycleVerbs(order []string, components []manifest.ComponentRef, componentsManifests []manifest.Manifest, verbs []string, stackBaseDir, componentsBaseDir string, skip func(int, string) bool) { optionalVerbs := []string{"backup"} for i, name := range order { if skip != nil && skip(i, name) { continue } component := manifest.ComponentRefByName(components, name) dir := manifest.ComponentSourceDirFromRef(component, stackBaseDir, componentsBaseDir) for _, verb := range verbs { if util.Contains(optionalVerbs, verb) { continue } componentManifest := manifest.ComponentManifestByRef(componentsManifests, component) impl, err := probeImplementation(dir, verb, componentManifest) if !impl { msg := fmt.Sprintf("`%s` component in `%s` has no `%s` implementation: %v", manifest.ComponentQualifiedNameFromRef(component), dir, verb, err) if componentManifest.Lifecycle.Bare == "allow" { if config.Debug { log.Print(msg) } } else { log.Fatalf("%s;\n\tTry setting `lifecycle.bare: allow` in component's manifest if it's your intention", msg) } } } } } func checkLifecycleOrder(components []manifest.ComponentRef, lifecycle manifest.Lifecycle) { refs := manifest.ComponentsNamesFromRefs(components) sorted := make([]string, len(refs)) copy(sorted, refs) order := make([]string, len(lifecycle.Order)) copy(order, lifecycle.Order) sort.Strings(sorted) sort.Strings(order) if !util.Equal(sorted, order) { lifecycleOrder := "(not specified)" if len(lifecycle.Order) > 0 { lifecycleOrder = strings.Join(lifecycle.Order, ", ") } log.Fatalf("Components: %s;\n\tdoes not match `lifecycle.order`: %s", strings.Join(refs, ", "), lifecycleOrder) } if len(lifecycle.Mandatory) > 0 && len(lifecycle.Optional) > 0 { util.Warn("Both\n\tlifecycle.mandatory = %v and\n\tlifecycle.optional = %v are set", lifecycle.Mandatory, lifecycle.Optional) } if !util.ContainsAll(sorted, lifecycle.Mandatory) { log.Fatalf("lifecycle.mandatory = %v does not match component list:\n\t%v", lifecycle.Mandatory, sorted) } if !util.ContainsAll(sorted, lifecycle.Optional) { log.Fatalf("lifecycle.optional = %v does not match component list:\n\t%v", lifecycle.Optional, sorted) } } func checkVerbs(componentName string, availableVerbs []string, verb string) { for _, supported := range availableVerbs { if verb == supported { return } } available := "(none)" if len(availableVerbs) > 0 { available = strings.Join(availableVerbs, ", ") } util.MaybeFatalf("Verb `%s` is not supported by component `%s`, available verbs: %s", verb, componentName, available) } func checkLifecycleRequires(components []manifest.ComponentRef, requires manifest.RequiresTuning) { refs := manifest.ComponentsNamesFromRefs(components) sorted := make([]string, len(refs)) copy(sorted, refs) sort.Strings(sorted) for _, req := range requires.Optional { i := strings.Index(req, ":") if i > 0 && i < len(req)-1 { component := req[i+1:] if !util.Contains(refs, component) { log.Fatalf("lifecycle.requires.optional = %s component `%s` does not match component list:\n\t%v", req, component, sorted) } } } } func checkComponentsDepends(components []manifest.ComponentRef, order []string) { for _, component := range components { if len(component.Depends) > 0 && !util.ContainsAll(order, component.Depends) { var missing []string for _, dep := range component.Depends { if !util.Contains(order, dep) { missing = append(missing, dep) } } log.Fatalf("Component `%s` `depends: %v` does not match component list", manifest.ComponentQualifiedNameFromRef(&component), missing) } } } func checkStateMatch(state *state.StateManifest, elaborate *manifest.Manifest, stackParameters parameters.LockedParameters) { errs := make([]error, 0) if state.Meta.Name != "" && state.Meta.Name != elaborate.Meta.Name { errs = append(errs, fmt.Errorf("State meta.name = `%s` does not match elaborate meta.name = `%s`", state.Meta.Name, elaborate.Meta.Name)) } parametersToMatch := []string{"cloud.kind", "cloud.region", "terraform.bucket.name", "dns.name", "dns.domain"} for _, name := range parametersToMatch { if stackParam, exist := stackParameters[name]; exist { for _, stateParam := range state.StackParameters { if stateParam.QName() == name { if util.String(stateParam.Value) != util.String(stackParam.Value) { errs = append(errs, fmt.Errorf("Parameter `%s` state value `%v` does not match stack parameter value `%v`", name, stateParam.Value, stackParam.Value)) } break } } } } if len(state.Components) > 2 { stateComponents := make([]string, 0, len(state.Components)) stackComponents := manifest.ComponentsNamesFromRefs(elaborate.Components) found := 0 for stateComponent := range state.Components { stateComponents = append(stateComponents, stateComponent) if util.Contains(stackComponents, stateComponent) { found++ } } if found < len(stateComponents)/2 { sort.Strings(stackComponents) sort.Strings(stateComponents) errs = append(errs, fmt.Errorf("State components:\n\t\t%s\n\t\t\tdoes not match stack components:\n\t\t%s", strings.Join(stateComponents, ", "), strings.Join(stackComponents, ", "))) } } if len(errs) > 0 { msg := fmt.Sprintf("State file does not match deployment manifest (elaborate):\n\t%s", util.Errors("\n\t", errs...)) if config.Force { util.Warn("%s", msg) } else { log.Fatal(msg) } } }