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 }