github.com/replicatedhq/ship@v0.55.0/pkg/lifecycle/kustomize/kustomizer.go (about)

     1  package kustomize
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"path"
     7  	"path/filepath"
     8  	"sort"
     9  	"time"
    10  
    11  	"github.com/go-kit/kit/log"
    12  	"github.com/go-kit/kit/log/level"
    13  	"github.com/pkg/errors"
    14  	"github.com/spf13/afero"
    15  	"github.com/spf13/viper"
    16  	"sigs.k8s.io/kustomize/pkg/patch"
    17  	ktypes "sigs.k8s.io/kustomize/pkg/types"
    18  
    19  	"github.com/replicatedhq/ship/pkg/api"
    20  	"github.com/replicatedhq/ship/pkg/constants"
    21  	"github.com/replicatedhq/ship/pkg/lifecycle"
    22  	"github.com/replicatedhq/ship/pkg/lifecycle/daemon/daemontypes"
    23  	"github.com/replicatedhq/ship/pkg/state"
    24  	"github.com/replicatedhq/ship/pkg/util"
    25  )
    26  
    27  func NewDaemonKustomizer(
    28  	logger log.Logger,
    29  	daemon daemontypes.Daemon,
    30  	fs afero.Afero,
    31  	stateManager state.Manager,
    32  	viper *viper.Viper,
    33  ) lifecycle.Kustomizer {
    34  	return &daemonkustomizer{
    35  		Kustomizer: Kustomizer{
    36  			Logger: logger,
    37  			FS:     fs,
    38  			State:  stateManager,
    39  			Viper:  viper,
    40  		},
    41  		Daemon: daemon,
    42  	}
    43  }
    44  
    45  // kustomizer will *try* to pull in the Kustomizer libs from kubernetes-sigs/kustomize,
    46  // if not we'll have to fork. for now it just explodes
    47  type daemonkustomizer struct {
    48  	Kustomizer
    49  	Daemon daemontypes.Daemon
    50  }
    51  
    52  func (l *daemonkustomizer) Execute(ctx context.Context, release *api.Release, step api.Kustomize) error {
    53  	daemonExitedChan := l.Daemon.EnsureStarted(ctx, release)
    54  	err := l.awaitKustomizeSaved(ctx, daemonExitedChan)
    55  	if err != nil {
    56  		return errors.Wrap(err, "ensure daemon \"started\"")
    57  	}
    58  
    59  	return l.Kustomizer.Execute(ctx, release, step)
    60  }
    61  
    62  // hack -- get the root path off a render step to tell if we should prefix kustomize outputs
    63  func (l *Kustomizer) getPotentiallyChrootedFs(release *api.Release) (afero.Afero, error) {
    64  	renderRoot := constants.InstallerPrefixPath
    65  	renderStep := release.FindRenderStep()
    66  	if renderStep == nil || renderStep.Root == "./" || renderStep.Root == "." {
    67  		return l.FS, nil
    68  	}
    69  	if renderStep.Root != "" {
    70  		renderRoot = renderStep.Root
    71  	}
    72  
    73  	fs := afero.Afero{Fs: afero.NewBasePathFs(l.FS, renderRoot)}
    74  	err := fs.MkdirAll("/", 0755)
    75  	if err != nil {
    76  		return afero.Afero{}, errors.Wrap(err, "mkdir fs root")
    77  	}
    78  	return fs, nil
    79  }
    80  
    81  func (l *daemonkustomizer) awaitKustomizeSaved(ctx context.Context, daemonExitedChan chan error) error {
    82  	debug := level.Debug(log.With(l.Logger, "struct", "kustomizer", "method", "kustomize.save.await"))
    83  	for {
    84  		select {
    85  		case <-ctx.Done():
    86  			debug.Log("event", "ctx.done")
    87  			return ctx.Err()
    88  		case err := <-daemonExitedChan:
    89  			debug.Log("event", "daemon.exit")
    90  			if err != nil {
    91  				return err
    92  			}
    93  			return errors.New("daemon exited")
    94  		case <-l.Daemon.KustomizeSavedChan():
    95  			debug.Log("event", "kustomize.finalized")
    96  			return nil
    97  		case <-time.After(10 * time.Second):
    98  			debug.Log("waitingFor", "kustomize.finalized")
    99  		}
   100  	}
   101  }
   102  
   103  func (l *Kustomizer) writePatches(
   104  	fs afero.Afero,
   105  	shipOverlay state.Overlay,
   106  	destDir string,
   107  ) (relativePatchPaths []patch.StrategicMerge, err error) {
   108  	patches, err := l.writeFileMap(fs, shipOverlay.Patches, destDir)
   109  	if err != nil {
   110  		return nil, errors.Wrapf(err, "write file map to %s", destDir)
   111  	}
   112  	for _, p := range patches {
   113  		relativePatchPaths = append(relativePatchPaths, patch.StrategicMerge(p))
   114  	}
   115  	return
   116  }
   117  
   118  func (l *Kustomizer) writeResources(fs afero.Afero, shipOverlay state.Overlay, destDir string) (relativeResourcePaths []string, err error) {
   119  	return l.writeFileMap(fs, shipOverlay.Resources, destDir)
   120  }
   121  
   122  func (l *Kustomizer) writeFileMap(fs afero.Afero, files map[string]string, destDir string) (paths []string, err error) {
   123  	debug := level.Debug(log.With(l.Logger, "method", "writeResources"))
   124  
   125  	var keys []string
   126  	for k := range files {
   127  		keys = append(keys, k)
   128  	}
   129  	sort.Strings(keys)
   130  
   131  	for _, file := range keys {
   132  		contents := files[file]
   133  
   134  		name := path.Join(destDir, file)
   135  		err := l.writeFile(fs, name, contents)
   136  		if err != nil {
   137  			debug.Log("event", "write", "name", name)
   138  			return []string{}, errors.Wrapf(err, "write resource %s", name)
   139  		}
   140  
   141  		relativePatchPath, err := filepath.Rel(destDir, name)
   142  		if err != nil {
   143  			return []string{}, errors.Wrap(err, "unable to determine relative path")
   144  		}
   145  		paths = append(paths, relativePatchPath)
   146  	}
   147  
   148  	return paths, nil
   149  
   150  }
   151  
   152  func (l *Kustomizer) writeFile(fs afero.Afero, name string, contents string) error {
   153  	debug := level.Debug(log.With(l.Logger, "method", "writeFile"))
   154  
   155  	destDir := filepath.Dir(name)
   156  
   157  	// make the dir
   158  	err := l.FS.MkdirAll(destDir, 0777)
   159  	if err != nil {
   160  		debug.Log("event", "mkdir.fail", "dir", destDir)
   161  		return errors.Wrapf(err, "make dir %s", destDir)
   162  	}
   163  
   164  	// write the file
   165  	err = l.FS.WriteFile(name, []byte(contents), 0666)
   166  	if err != nil {
   167  		return errors.Wrapf(err, "write patch %s", name)
   168  	}
   169  	debug.Log("event", "patch.written", "patch", name)
   170  	return nil
   171  }
   172  
   173  func (l *Kustomizer) writeOverlay(
   174  	step api.Kustomize,
   175  	relativePatchPaths []patch.StrategicMerge,
   176  	relativeResourcePaths []string,
   177  	kustomization ktypes.Kustomization,
   178  ) error {
   179  	// just always make a new kustomization.yaml for now
   180  	basePath, err := filepath.Rel(step.OverlayPath(), constants.DefaultOverlaysPath)
   181  	if err != nil {
   182  		return err
   183  	}
   184  	kustomization.Bases = []string{basePath}
   185  	kustomization.PatchesStrategicMerge = relativePatchPaths
   186  	kustomization.Resources = relativeResourcePaths
   187  
   188  	marshalled, err := util.MarshalIndent(2, kustomization)
   189  	if err != nil {
   190  		return errors.Wrap(err, "marshal kustomization.yaml")
   191  	}
   192  
   193  	name := path.Join(step.OverlayPath(), "kustomization.yaml")
   194  	err = l.FS.WriteFile(name, []byte(marshalled), 0666)
   195  	if err != nil {
   196  		return errors.Wrapf(err, "write file %s", name)
   197  	}
   198  
   199  	return nil
   200  }
   201  
   202  func (l *Kustomizer) writeBase(base string) error {
   203  	debug := level.Debug(log.With(l.Logger, "method", "writeBase"))
   204  
   205  	currentState, err := l.State.CachedState()
   206  	if err != nil {
   207  		return errors.Wrap(err, "load state")
   208  	}
   209  
   210  	currentKustomize := currentState.CurrentKustomize()
   211  	if currentKustomize == nil {
   212  		currentKustomize = &state.Kustomize{}
   213  	}
   214  	shipOverlay := currentKustomize.Ship()
   215  
   216  	baseKustomization := ktypes.Kustomization{}
   217  	if err := l.FS.Walk(
   218  		base,
   219  		func(targetPath string, info os.FileInfo, err error) error {
   220  			if err != nil {
   221  				debug.Log("event", "walk.fail", "path", targetPath)
   222  				return errors.Wrap(err, "failed to walk path")
   223  			}
   224  			relativePath, err := filepath.Rel(base, targetPath)
   225  			if err != nil {
   226  				debug.Log("event", "relativepath.fail", "base", base, "target", targetPath)
   227  				return errors.Wrap(err, "failed to get relative path")
   228  			}
   229  			if l.shouldAddFileToBase(base, shipOverlay.ExcludedBases, relativePath) {
   230  				baseKustomization.Resources = append(baseKustomization.Resources, relativePath)
   231  			}
   232  			return nil
   233  		},
   234  	); err != nil {
   235  		return err
   236  	}
   237  
   238  	if len(baseKustomization.Resources) == 0 {
   239  		return errors.New("Base directory is empty")
   240  	}
   241  
   242  	marshalled, err := util.MarshalIndent(2, baseKustomization)
   243  	if err != nil {
   244  		return errors.Wrap(err, "marshal base kustomization.yaml")
   245  	}
   246  
   247  	// write base kustomization
   248  	name := path.Join(base, "kustomization.yaml")
   249  	err = l.FS.WriteFile(name, []byte(marshalled), 0666)
   250  	if err != nil {
   251  		return errors.Wrapf(err, "write file %s", name)
   252  	}
   253  	return nil
   254  }
   255  
   256  func (l *Kustomizer) shouldAddFileToBase(basePath string, excludedBases []string, targetPath string) bool {
   257  	baseFs := afero.Afero{Fs: afero.NewBasePathFs(l.FS, basePath)}
   258  	return util.ShouldAddFileToBase(&baseFs, excludedBases, targetPath)
   259  }