Skip to content
Snippets Groups Projects
Commit 15de5366d86d authored by Florent Aide's avatar Florent Aide
Browse files

cleanup .orig

parent 13cab2b329ba
No related branches found
No related tags found
No related merge requests found
Pipeline #42654 failed
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
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment