cmd/hub/lifecycle/probe.go (217 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 ( "errors" "fmt" "io" "log" "os" "os/exec" "path/filepath" "strings" "github.com/epam/hubctl/cmd/hub/config" "github.com/epam/hubctl/cmd/hub/ext" "github.com/epam/hubctl/cmd/hub/manifest" "github.com/epam/hubctl/cmd/hub/util" ) var hubToSkaffoldVerbs = map[string]string{ "deploy": "run", "undeploy": "delete", } func findImplementation(dir string, verb string, component *manifest.Manifest) (*exec.Cmd, error) { makefile, err := probeMakefile(dir, verb) if makefile { binMake, err := exec.LookPath("make") if err != nil { binMake = "/usr/bin/make" util.WarnOnce("Unable to lookup `make` in PATH: %v; trying `%s`", err, binMake) } return &exec.Cmd{Path: binMake, Args: []string{"make", verb}, Dir: dir}, nil } script, err2 := probeScript(dir, verb) if script != "" { return &exec.Cmd{Path: script, Dir: dir}, nil } helm, args, err3 := probeHelm(dir, verb) if helm != "" { return &exec.Cmd{Path: helm, Args: append([]string{helm}, args...), Dir: dir}, nil } kustomize, args, err4 := probeKustomize(dir, verb) if kustomize != "" { return &exec.Cmd{Path: kustomize, Args: append([]string{kustomize}, args...), Dir: dir}, nil } skaffold, err5 := probeSkaffold(dir, verb) if skaffold { binSkaffold, err := exec.LookPath("skaffold") if err != nil { binSkaffold = "/usr/local/bin/skaffold" util.WarnOnce("Unable to lookup `skaffold` in PATH: %v; trying `%s`", err, binSkaffold) } if translatedVerb, translated := hubToSkaffoldVerbs[verb]; translated { verb = translatedVerb } return &exec.Cmd{Path: binSkaffold, Args: []string{"skaffold", verb}, Dir: dir}, nil } terraform, args, err6 := probeTerraform(dir, verb) if terraform != "" { return &exec.Cmd{Path: terraform, Args: append([]string{terraform}, args...), Dir: dir}, nil } arm, args, err7 := probeArm(dir, verb, component) if arm != "" { return &exec.Cmd{Path: arm, Args: append([]string{arm}, args...), Dir: dir}, nil } return nil, fmt.Errorf("No `%s` implementation found in `%s`: %s", verb, dir, util.Errors("; ", err, err2, err3, err4, err5, err6, err7)) } func probeArm(dir string, verb string, component *manifest.Manifest) (string, []string, error) { if util.Contains(component.Requires, "arm") { return ext.ExtensionPath([]string{"component", "arm", verb}, nil) } return "", []string{verb}, nil } func probeImplementation(dir string, verb string, component *manifest.Manifest) (bool, error) { makefile, err := probeMakefile(dir, verb) if makefile { return true, nil } script, err2 := probeScript(dir, verb) if script != "" { return true, nil } helm, _, err3 := probeHelm(dir, verb) if helm != "" { return true, nil } helm, _, err4 := probeKustomize(dir, verb) if helm != "" { return true, nil } skaffold, err5 := probeSkaffold(dir, verb) if skaffold { return true, nil } terraform, _, err6 := probeTerraform(dir, verb) if terraform != "" { return true, nil } arm, _, err7 := probeArm(dir, verb, component) if arm != "" { return true, nil } allErrs := util.Errors("; ", err, err2, err3, err4, err5, err6, err7) if config.Debug { log.Printf("Found no `%s` implementations in `%s`: %s", verb, dir, allErrs) } return false, errors.New(allErrs) } // TODO folowing probes relies on impl file presence // that may not be the case if the impl is templated func probeMakefile(dir string, verb string) (bool, error) { filename := fmt.Sprintf("%s/Makefile", dir) makefile, err := os.Open(filename) if err != nil { if os.IsNotExist(err) { return false, nil } return false, fmt.Errorf("%s: %v", filename, err) } bytes, err := io.ReadAll(makefile) if err != nil { return false, fmt.Errorf("%s: %v", filename, err) } text := string(bytes) if strings.HasPrefix(text, verb+":") || strings.Contains(text, "\n"+verb+":") { return true, nil } return false, nil } func probeScript(dir string, verb string) (string, error) { scripts := []string{verb, "bin/" + verb, "_" + verb, verb + ".sh", "bin/" + verb + ".sh", "_" + verb + ".sh"} var lastErr error = nil for _, script := range scripts { filename := fmt.Sprintf("%s/%s", dir, script) info, err := os.Stat(filename) if err != nil { if !os.IsNotExist(err) { lastErr = err } continue } mode := info.Mode() if mode.IsRegular() { // TODO check exec bits? return script, nil } } return "", lastErr } func probeHelm(dir string, verb string) (string, []string, error) { return probeExtension(dir, verb, "helm", []string{"values.yaml", "values.yaml.template", "values.yaml.gotemplate"}) } func probeKustomize(dir string, verb string) (string, []string, error) { return probeExtension(dir, verb, "kustomize", []string{"kustomization.yaml", "kustomization.yaml.template", "kustomization.yaml.gotemplate"}) } func probeExtension(dir, verb, extension string, files []string) (string, []string, error) { var lastErr error = nil found := false for _, yaml := range files { filename := fmt.Sprintf("%s/%s", dir, yaml) info, err := os.Stat(filename) if err != nil { if !os.IsNotExist(err) { lastErr = err } continue } mode := info.Mode() if mode.IsRegular() { found = true break } } if found { return ext.ExtensionPath([]string{"component", extension, verb}, nil) } return "", nil, lastErr } func probeSkaffold(dir string, verb string) (bool, error) { yamls := []string{"skaffold.yaml", "skaffold.yaml.template", "skaffold.yaml.gotemplate"} var lastErr error = nil for _, yaml := range yamls { filename := fmt.Sprintf("%s/%s", dir, yaml) info, err := os.Stat(filename) if err != nil { if !os.IsNotExist(err) { lastErr = err } continue } mode := info.Mode() if mode.IsRegular() { return true, nil } } return false, lastErr } func probeTerraform(dir string, verb string) (string, []string, error) { globs := []string{"*.tf", "*.tf.*"} var lastErr error = nil found := false for _, glob := range globs { pattern := fmt.Sprintf("%s/%s", dir, glob) matches, err := filepath.Glob(pattern) if err != nil { lastErr = err continue } if len(matches) > 0 { found = true break } } if found { path, args, err := ext.ExtensionPath([]string{"component", "terraform", verb}, nil) return path, args, err } return "", nil, lastErr }