github.com/replicatedhq/ship@v0.55.0/pkg/lifecycle/render/render.go (about) 1 package render 2 3 import ( 4 "context" 5 "time" 6 7 "github.com/go-kit/kit/log" 8 "github.com/go-kit/kit/log/level" 9 "github.com/mitchellh/cli" 10 "github.com/pkg/errors" 11 "github.com/replicatedhq/libyaml" 12 "github.com/spf13/afero" 13 14 "github.com/replicatedhq/ship/pkg/api" 15 "github.com/replicatedhq/ship/pkg/constants" 16 "github.com/replicatedhq/ship/pkg/lifecycle/daemon/daemontypes" 17 "github.com/replicatedhq/ship/pkg/lifecycle/render/config" 18 pkgplanner "github.com/replicatedhq/ship/pkg/lifecycle/render/planner" 19 "github.com/replicatedhq/ship/pkg/state" 20 ) 21 22 var ( 23 ProgressRead = daemontypes.StringProgress("render", "reading application release") 24 ProgressRender = daemontypes.StringProgress("render", "rendering assets and configuration values") 25 ) 26 27 // A headlessrenderer takes a resolved spec, collects config values, and renders assets 28 // this was the old plain "renderer", but now its only used in headless workflows, so renaming it for clarity 29 type headlessrenderer struct { 30 Logger log.Logger 31 ConfigResolver config.Resolver 32 Planner pkgplanner.Planner 33 StateManager state.Manager 34 Fs afero.Afero 35 UI cli.Ui 36 StatusReceiver daemontypes.StatusReceiver 37 Now func() time.Time 38 } 39 40 // Execute renders the assets and config 41 func (r *headlessrenderer) Execute(ctx context.Context, release *api.Release, step *api.Render) error { 42 defer r.StatusReceiver.ClearProgress() 43 44 debug := level.Debug(log.With(r.Logger, "step.type", "render")) 45 debug.Log("event", "step.execute") 46 47 debug.Log("event", "try.load") 48 previousState, err := r.StateManager.CachedState() 49 if err != nil { 50 return err 51 } 52 53 r.StatusReceiver.SetProgress(ProgressRead) 54 55 debug.Log("event", "resolve.config") 56 currentConfig, err := previousState.CurrentConfig() 57 if err != nil { 58 return errors.Wrap(err, "get current config") 59 } 60 61 templateContext, err := r.ConfigResolver.ResolveConfig(ctx, release, currentConfig) 62 if err != nil { 63 return errors.Wrap(err, "resolve config") 64 } 65 66 r.StatusReceiver.SetProgress(ProgressRender) 67 68 // this should probably happen even higher up, like to the validation stage where we assign IDs to lifecycle steps, but moving it up here for now 69 if step.Root == "" { 70 step.Root = constants.InstallerPrefixPath 71 } 72 73 assets := release.Spec.Assets.V1 74 if step.Assets != nil && step.Assets.V1 != nil { 75 assets = step.Assets.V1 76 } 77 78 debug.Log("event", "render.plan") 79 pln, dests, err := r.Planner.Build(step.Root, assets, release.Spec.Config.V1, release.Metadata, templateContext) 80 if err != nil { 81 return errors.Wrap(err, "build plan") 82 } 83 84 debug.Log("event", "clean.start") 85 // cleanup dests 86 err = removeDests(&r.Fs, dests) 87 if err != nil { 88 return errors.Wrap(err, "clean destination") 89 } 90 91 if step.Root != "." && step.Root != "./" { 92 debug.Log("event", "backup.start") 93 err = r.backupIfPresent(step.Root) 94 if err != nil { 95 return errors.Wrapf(err, "backup existing install directory %s", constants.InstallerPrefixPath) 96 } 97 } 98 99 debug.Log("event", "execute.plan") 100 r.StatusReceiver.SetStepName(ctx, daemontypes.StepNameConfirm) 101 err = r.Planner.Execute(ctx, pln) 102 if err != nil { 103 return errors.Wrap(err, "execute plan") 104 } 105 106 currentConfig, err = previousState.CurrentConfig() 107 if err != nil { 108 return errors.Wrap(err, "get current config") 109 } 110 111 stateTemplateContext := make(map[string]interface{}) 112 for _, configGroup := range release.Spec.Config.V1 { 113 for _, configItem := range configGroup.Items { 114 if valueNotOverriddenByDefault(configItem, templateContext, currentConfig) { 115 stateTemplateContext[configItem.Name] = templateContext[configItem.Name] 116 } 117 } 118 } 119 120 // edge case: empty config section of app yaml, 121 // persist data from previous state.json 122 if len(release.Spec.Config.V1) == 0 { 123 stateTemplateContext = templateContext 124 } 125 126 debug.Log("event", "commit") 127 if err := r.StateManager.SerializeConfig(release.Spec.Assets.V1, release.Metadata, stateTemplateContext); err != nil { 128 return errors.Wrap(err, "serialize state") 129 } 130 131 return nil 132 } 133 134 func valueNotOverriddenByDefault(item *libyaml.ConfigItem, templateContext map[string]interface{}, savedState map[string]interface{}) bool { 135 _, inSavedState := savedState[item.Name] // all values in savedState are non-default values 136 137 if templateContext[item.Name] == "" { 138 if inSavedState && savedState[item.Name] == "" { 139 // manually set value: "" in state.json 140 return true 141 } else { 142 // value overridden by default == ""? 143 return item.Default != "" 144 } 145 } else if templateContext[item.Name] == item.Default { 146 // the provided value is manually set to the default value 147 return inSavedState 148 } else { 149 // non-empty value != default. cannot have been overridden by default 150 return true 151 } 152 }