get.porter.sh/porter@v1.3.0/pkg/runtime/runtime.go (about) 1 package runtime 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "path/filepath" 8 9 "get.porter.sh/porter/pkg" 10 "get.porter.sh/porter/pkg/cnab" 11 "get.porter.sh/porter/pkg/config" 12 "get.porter.sh/porter/pkg/manifest" 13 "get.porter.sh/porter/pkg/pkgmgmt" 14 "get.porter.sh/porter/pkg/portercontext" 15 "get.porter.sh/porter/pkg/yaml" 16 "github.com/cnabio/cnab-to-oci/relocation" 17 "github.com/hashicorp/go-multierror" 18 ) 19 20 // PorterRuntime orchestrates executing a bundle and managing state. 21 type PorterRuntime struct { 22 config RuntimeConfig 23 mixins pkgmgmt.PackageManager 24 RuntimeManifest *RuntimeManifest 25 } 26 27 func NewPorterRuntime(runtimeCfg RuntimeConfig, mixins pkgmgmt.PackageManager) *PorterRuntime { 28 return &PorterRuntime{ 29 config: runtimeCfg, 30 mixins: mixins, 31 } 32 } 33 34 func (r *PorterRuntime) Execute(ctx context.Context, rm *RuntimeManifest) error { 35 r.RuntimeManifest = rm 36 37 installationName := r.config.Getenv(config.EnvInstallationName) 38 bundleName := r.config.Getenv(config.EnvBundleName) 39 fmt.Fprintf(r.config.Out, "executing %s action from %s (installation: %s)\n", r.RuntimeManifest.Action, bundleName, installationName) 40 41 err := r.RuntimeManifest.Validate() 42 if err != nil { 43 return err 44 } 45 46 // Prepare prepares the runtime environment prior to step execution. 47 // As an example, for parameters of type "file", we may need to decode file contents 48 // on the filesystem before execution of the step/action 49 err = r.RuntimeManifest.Initialize(ctx) 50 if err != nil { 51 return err 52 } 53 54 // Update the runtimeManifest images with the bundle.json and relocation mapping (if it's there) 55 rtb, reloMap, err := r.getImageMappingFiles() 56 if err != nil { 57 return err 58 } 59 60 err = r.RuntimeManifest.ResolveInvocationImage(rtb, reloMap) 61 if err != nil { 62 return fmt.Errorf("unable to resolve bundle images: %w", err) 63 } 64 err = r.RuntimeManifest.ResolveImages(rtb, reloMap) 65 if err != nil { 66 return fmt.Errorf("unable to resolve bundle images: %w", err) 67 } 68 69 err = r.config.FileSystem.MkdirAll(portercontext.MixinOutputsDir, pkg.FileModeDirectory) 70 if err != nil { 71 return fmt.Errorf("could not create outputs directory %s: %w", portercontext.MixinOutputsDir, err) 72 } 73 74 var bigErr *multierror.Error 75 for stepIndex, step := range r.RuntimeManifest.GetSteps() { 76 err = r.executeStep(ctx, stepIndex, step) 77 if err != nil { 78 bigErr = multierror.Append(bigErr, err) 79 break 80 } 81 } 82 83 err = r.RuntimeManifest.Finalize(ctx) 84 if err != nil { 85 bigErr = multierror.Append(bigErr, err) 86 } 87 88 return bigErr.ErrorOrNil() 89 } 90 91 func (r *PorterRuntime) executeStep(ctx context.Context, stepIndex int, step *manifest.Step) error { 92 if step == nil { 93 return nil 94 } 95 err := r.RuntimeManifest.ResolveStep(ctx, stepIndex, step) 96 if err != nil { 97 return fmt.Errorf("unable to resolve step: %w", err) 98 } 99 100 description, _ := step.GetDescription() 101 if len(description) > 0 { 102 fmt.Fprintln(r.config.Out, description) 103 } 104 105 // Hand over values needing masking in config output streams 106 r.config.Context.SetSensitiveValues(r.RuntimeManifest.GetSensitiveValues()) 107 108 input := &ActionInput{ 109 action: r.RuntimeManifest.Action, 110 Steps: []*manifest.Step{step}, 111 } 112 inputBytes, _ := yaml.Marshal(input) 113 cmd := pkgmgmt.CommandOptions{ 114 Command: string(r.RuntimeManifest.Action), 115 Input: string(inputBytes), 116 Runtime: true, 117 } 118 err = r.mixins.Run(ctx, r.config.Context, step.GetMixinName(), cmd) 119 if err != nil { 120 return fmt.Errorf("mixin execution failed: %w", err) 121 } 122 123 outputs, err := r.readMixinOutputs() 124 if err != nil { 125 return fmt.Errorf("could not read step outputs: %w", err) 126 } 127 128 err = r.RuntimeManifest.ApplyStepOutputs(outputs) 129 if err != nil { 130 return err 131 } 132 133 // Apply any Bundle Outputs declared in this step 134 return r.applyStepOutputsToBundle(outputs) 135 } 136 137 // applyStepOutputsToBundle writes the provided step outputs to the proper location 138 // in the bundle execution environment. 139 func (r *PorterRuntime) applyStepOutputsToBundle(outputs map[string]string) error { 140 for outputKey, outputValue := range outputs { 141 bundleOutput, ok := r.RuntimeManifest.Outputs[outputKey] 142 if !ok { 143 continue 144 } 145 146 if r.shouldApplyOutput(bundleOutput) { 147 outpath := filepath.Join(config.BundleOutputsDir, bundleOutput.Name) 148 149 err := r.config.FileSystem.WriteFile(outpath, []byte(outputValue), pkg.FileModeWritable) 150 if err != nil { 151 return fmt.Errorf("unable to write output file %s: %w", outpath, err) 152 } 153 } 154 } 155 return nil 156 } 157 158 func (r *PorterRuntime) shouldApplyOutput(output manifest.OutputDefinition) bool { 159 if len(output.ApplyTo) == 0 { 160 return true 161 } 162 163 for _, applyTo := range output.ApplyTo { 164 if string(r.RuntimeManifest.Action) == applyTo { 165 return true 166 } 167 } 168 return false 169 } 170 171 func (r *PorterRuntime) readMixinOutputs() (map[string]string, error) { 172 outputs := map[string]string{} 173 174 outfiles, err := r.config.FileSystem.ReadDir(portercontext.MixinOutputsDir) 175 if err != nil { 176 return nil, fmt.Errorf("could not list %s: %w", portercontext.MixinOutputsDir, err) 177 } 178 179 for _, outfile := range outfiles { 180 if outfile.IsDir() { 181 continue 182 } 183 outpath := filepath.Join(portercontext.MixinOutputsDir, outfile.Name()) 184 contents, err := r.config.FileSystem.ReadFile(outpath) 185 if err != nil { 186 return nil, fmt.Errorf("could not read output file %s: %w", outpath, err) 187 } 188 189 outputs[outfile.Name()] = string(contents) 190 191 err = r.config.FileSystem.Remove(outpath) 192 if err != nil { 193 return nil, err 194 } 195 } 196 197 return outputs, nil 198 } 199 200 func (r *PorterRuntime) getImageMappingFiles() (cnab.ExtendedBundle, relocation.ImageRelocationMap, error) { 201 // TODO(carolynvs): switch to returning a BundleReference 202 b, err := cnab.LoadBundle(r.config.Context, "/cnab/bundle.json") 203 if err != nil { 204 return cnab.ExtendedBundle{}, nil, err 205 } 206 207 var reloMap relocation.ImageRelocationMap 208 if _, err := r.config.FileSystem.Stat("/cnab/app/relocation-mapping.json"); err == nil { 209 reloBytes, err := r.config.FileSystem.ReadFile("/cnab/app/relocation-mapping.json") 210 if err != nil { 211 return cnab.ExtendedBundle{}, nil, fmt.Errorf("couldn't read relocation file: %w", err) 212 } 213 err = json.Unmarshal(reloBytes, &reloMap) 214 if err != nil { 215 return cnab.ExtendedBundle{}, nil, fmt.Errorf("couldn't load relocation file: %w", err) 216 } 217 } 218 return b, reloMap, nil 219 } 220 221 func (r *PorterRuntime) NewRuntimeManifest(action string, m *manifest.Manifest) *RuntimeManifest { 222 return NewRuntimeManifest(r.config, action, m) 223 }