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  }