diff --git a/runner/main.go.orig b/runner/main.go.orig deleted file mode 100644 index 13cab2b329ba02123cf3698a0e03c0a5fee20052_cnVubmVyL21haW4uZ28ub3JpZw==..0000000000000000000000000000000000000000 --- a/runner/main.go.orig +++ /dev/null @@ -1,384 +0,0 @@ -package runner - -import ( - "bytes" - "errors" - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - "strings" - - "github.com/go-cmd/cmd" - "github.com/go-yaml/yaml" -) - -var ( - defaultFileMod os.FileMode = 0600 - defaultDirMod os.FileMode = 0700 - // TODO: find commands full path - yttCmd = "ytt" - helmCmd = "helm" - kubectlCmd = "kubectl" -) - -// Runner is the struct in charge of launching commands -type Runner struct { - config *CmdConfig -} - -// NewRunner ... -func NewRunner(cfg *CmdConfig) *Runner { - return &Runner{ - config: cfg, - } -} - -// Build is in charge of applying commands based on the config data -func (r *Runner) Build(tmpDir string) error { - var outputDir string - if r.config.Output == "" { - outputDir = filepath.Join(r.config.RootDir, "build", r.config.Namespace) - } else { - outputDir = r.config.Output - } - - preBuildDir := filepath.Join(tmpDir, "pre-build") - if err := r.DoBuild(tmpDir, preBuildDir); err != nil { - return fmt.Errorf("failed to do pre-build: %w", err) - } - if err := r.config.SetShas(preBuildDir); err != nil { - return fmt.Errorf("failed to set SHAs: %w", err) - } - files, err := ioutil.ReadDir(preBuildDir) - if err != nil { - return fmt.Errorf("cannot list directory: %s - %w", preBuildDir, err) - } - if err := CleanDir(outputDir); err != nil { - return fmt.Errorf("cannot clean dir: %s: %w", outputDir, err) - } - variables, err := r.config.prepareVariables(true) - if err != nil { - return fmt.Errorf("cannot prepare variables: %w", err) - } - for _, file := range files { - inFilePath := filepath.Join(preBuildDir, file.Name()) - outFilePath := filepath.Join(outputDir, file.Name()) - outFile, err := os.Create(outFilePath) - if err != nil { - return fmt.Errorf("cannot open: %s - %w", outFilePath, err) - } - defer func() { - if err := outFile.Close(); err != nil { - r.config.Logger.Fatal().Err(err).Msg("cannot close hydrated file") - } - }() - if err := hydrate(inFilePath, outFile, variables); err != nil { - return fmt.Errorf("cannot hydrate: %s - %w", outFilePath, err) - } - } - return nil -} - -func (r *Runner) DoBuild(tmpDir, outputDir string) error { - cmds, err := r.prepareCmds() - if err != nil { - return err - } - - compiled, err := r.runCommands(tmpDir, cmds) - if err != nil { - return err - } - - yttOutput, err := r.runYtt(tmpDir, compiled) - if err != nil { - return err - } - - kustomizeOutput, err := r.kustomize(tmpDir, yttOutput) - if err != nil { - return err - } - - if r.config.DryRun { - return nil - } - - if err := CleanDir(outputDir); err != nil { - return fmt.Errorf("cannot clean dir: %s: %w", outputDir, err) - } - if _, err := YamlSplit(outputDir, kustomizeOutput.Name()); err != nil { - return fmt.Errorf("cannot split full compiled file: %w", err) - } - - return nil -} - -func (r *Runner) kustomize(tmpDir string, input *os.File) (*os.File, error) { - kustomizeFilePath := filepath.Join(tmpDir, "kustomization.yaml") - f, err := os.Create(kustomizeFilePath) - if err != nil { - return nil, fmt.Errorf("fail to open %s: %w", kustomizeFilePath, err) - } - _, err = f.Write([]byte(fmt.Sprintf("resources: [%s]", filepath.Base(input.Name())))) - if err != nil { - return nil, fmt.Errorf("fail to write %s: %w", kustomizeFilePath, err) - } - - variables, err := r.config.prepareVariables(false) - if err != nil { - return nil, fmt.Errorf("cannot prepare kustomize variables: %w", err) - } - - var lastKustomizeFolder string - - for _, layer := range r.config.Layers { - for _, ext := range []string{"yml", "yaml"} { - fName := fmt.Sprintf("kustomization.%s", ext) - fPath := filepath.Join(layer, "kustomize", fName) - - fStat, err := os.Stat(fPath) - if err != nil || fStat.IsDir() { - continue - } - backupFile := fmt.Sprintf("%s.back", fPath) - if err := Copy(fPath, backupFile); err != nil { - return nil, fmt.Errorf("cannot copy kustomization file: %w", err) - } - defer func(fPath string) { - if err := Copy(backupFile, fPath); err != nil { - r.config.Logger.Fatal().Err(err).Msg("cannot restore kustomization back file") - } - if err := os.Remove(backupFile); err != nil { - r.config.Logger.Fatal().Err(err).Msg("cannot remove kustomization back file") - } - }(fPath) - - if err := os.Remove(fPath); err != nil { - return nil, fmt.Errorf("cannot remove original kustomization file: %w", err) - } - outFile, err := os.Create(fPath) - if err != nil { - return nil, fmt.Errorf("cannot open: %s - %w", fPath, err) - } - defer func() { - if err := outFile.Close(); err != nil { - r.config.Logger.Fatal().Err(err).Msg("cannot close hydrated kustomization file") - } - }() - - // kustomize root cannot be absolute - RelInputFilePath, err := filepath.Rel(filepath.Join(layer, "kustomize"), tmpDir) - if err != nil { - return nil, fmt.Errorf("cannot find relative path for: %s - %w", tmpDir, err) - } - variables["beaver.build"] = RelInputFilePath - - if err := hydrate(backupFile, outFile, variables); err != nil { - return nil, fmt.Errorf("cannot hydrate: %s - %w", fPath, err) - } - lastKustomizeFolder = filepath.Join(layer, "kustomize") - } - } - - // now run customize on the last layer with a kustomize folder - if lastKustomizeFolder != "" { - // now run customize on the last layer with a kustomize folder - kustomizeCmd := cmd.NewCmd(kubectlCmd, []string{"kustomize", lastKustomizeFolder}...) - return r.runCommand(tmpDir, "kustomize", kustomizeCmd) - } - - return input, nil -} - -func (r *Runner) prepareCmds() (map[string]*cmd.Cmd, error) { - // create helm commands - // create ytt chart commands - cmds := make(map[string]*cmd.Cmd) - for name, chart := range r.config.Spec.Charts { - args, err := chart.BuildArgs(name, r.config.Namespace) - if err != nil { - return nil, fmt.Errorf("build: failed to build args %w", err) - } - switch chart.Type { - case HelmType: - cmds[name] = cmd.NewCmd(helmCmd, args...) - case YttType: - cmds[name] = cmd.NewCmd(yttCmd, args...) - default: - return nil, fmt.Errorf("unsupported chart %s type: %q", chart.Path, chart.Type) - } - } - - for key, create := range r.config.Spec.Creates { - strArgs := key.BuildArgs(r.config.Namespace, create.Args) - name := fmt.Sprintf("%s_%s", key.Type, key.Name) - c := cmd.NewCmd(kubectlCmd, strArgs...) - c.Dir = create.Dir - cmds[name] = c - } - - return cmds, nil -} - -func (r *Runner) runCommand(tmpDir, name string, cmd *cmd.Cmd) (*os.File, error) { - r.config.Logger.Info(). - Str("command", cmd.Name). - Str("args", strings.Join(cmd.Args, " ")). - Msg("will run command") - - if r.config.DryRun { - r.config.Logger.Info(). - Str("command", cmd.Name). - Str("args", strings.Join(cmd.Args, " ")). - Msg("would run command") - return nil, nil - } - stdOut, stdErr, err := RunCMD(cmd) - if err != nil { - r.config.Logger.Err(err). - Str("command", cmd.Name). - Str("args", strings.Join(cmd.Args, " ")). - Str("sdtout", strings.Join(stdOut, "\n")). - Str("stderr", strings.Join(stdErr, "\n")). - Msg("failed to run command") - - // Error must be pretty printed to end users /!\ - fmt.Printf("\n%s\n\n", strings.Join(stdErr, "\n")) - return nil, fmt.Errorf("failed to run command: %w", err) - } - tmpFile, err := ioutil.TempFile(tmpDir, fmt.Sprintf("compiled-%s-*.yaml", name)) - if err != nil { - return nil, fmt.Errorf("cannot create compiled file: %w", err) - } - defer func() { - if err := tmpFile.Close(); err != nil { - r.config.Logger. - Err(err). - Str("temp file", tmpFile.Name()). - Msg("failed to close temp file") - } - }() - if _, err := tmpFile.WriteString(strings.Join(stdOut, "\n")); err != nil { - return nil, fmt.Errorf("cannot write compiled file: %w", err) - } - return tmpFile, nil -} - -func (r *Runner) runCommands(tmpDir string, cmds map[string]*cmd.Cmd) ([]string, error) { - var compiled []string - for name, cmd := range cmds { - f, err := r.runCommand(tmpDir, name, cmd) - if err != nil { - return nil, err - } - compiled = append(compiled, f.Name()) - } - return compiled, nil -} - -func (r *Runner) runYtt(tmpDir string, compiled []string) (*os.File, error) { - // create ytt additional command - args := r.config.BuildYttArgs(r.config.Spec.Ytt, compiled) - - yttExtraCmd := cmd.NewCmd(yttCmd, args...) - return r.runCommand(tmpDir, "ytt", yttExtraCmd) -} - -func CleanDir(directory string) error { - if err := os.RemoveAll(directory); err != nil { - return fmt.Errorf("cannot cleanup output directory: %w", err) - } - if err := os.MkdirAll(directory, defaultDirMod); err != nil { - return fmt.Errorf("cannot create output directory: %w", err) - } - return nil -} - -func Copy(src, dst string) error { - in, err := os.Open(src) - if err != nil { - return err - } - defer in.Close() - - out, err := os.Create(dst) - if err != nil { - return err - } - defer out.Close() - - _, err = io.Copy(out, in) - if err != nil { - return err - } - return out.Close() -} - -// YamlSplit takes a buildDir and an inputFile. -// it returns a list of yaml documents and an eventual error -func YamlSplit(buildDir, inputFile string) ([]string, error) { - var docs []string - var allResources []map[string]interface{} - input, err := os.ReadFile(inputFile) - if err != nil { - return nil, err - } - if err := unmarshalAllResources(input, &allResources); err != nil { - return nil, err - } - for _, resource := range allResources { - apiVersion, ok := resource["apiVersion"].(string) - if !ok { - return nil, fmt.Errorf("fail to type assert apiVersion from: %+v", resource) - } - kind, ok := resource["kind"].(string) - if !ok { - return nil, fmt.Errorf("kind missing from: %+v", resource) - } - metadata, ok := resource["metadata"].(map[interface{}]interface{}) - if !ok { - return nil, fmt.Errorf("fail to type assert metadata from: %+v", resource) - } - name, ok := metadata["name"].(string) - if !ok { - return nil, fmt.Errorf("fail to type assert metadata.name from: %+v", resource) - } - filename := fmt.Sprintf("%s.%s.%s.yaml", kind, strings.ReplaceAll(apiVersion, "/", "_"), name) - fPath := filepath.Join(buildDir, filename) - - out, err := yaml.Marshal(resource) - if err != nil { - return nil, fmt.Errorf("cannot marshal resource: %w", err) - } - if err := os.MkdirAll(buildDir, defaultDirMod); err != nil { - return nil, fmt.Errorf("cannot create build directory: %w", err) - } - content := append([]byte("---\n"), out...) - if err := os.WriteFile(fPath, content, defaultFileMod); err != nil { - return nil, fmt.Errorf("cannot write resource: %w", err) - } - docs = append(docs, fPath) - } - - return docs, nil -} - -func unmarshalAllResources(in []byte, out *[]map[string]interface{}) error { - r := bytes.NewReader(in) - decoder := yaml.NewDecoder(r) - for { - res := make(map[string]interface{}) - if err := decoder.Decode(&res); err != nil { - // Break when there are no more documents to decode - if !errors.Is(err, io.EOF) { - return err - } - break - } - *out = append(*out, res) - } - return nil -}