get.porter.sh/porter@v1.3.0/pkg/cnab/config-adapter/adapter.go (about)

     1  package configadapter
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"path"
     7  	"strings"
     8  
     9  	"get.porter.sh/porter/pkg/cnab"
    10  	depsv1ext "get.porter.sh/porter/pkg/cnab/extensions/dependencies/v1"
    11  	depsv2ext "get.porter.sh/porter/pkg/cnab/extensions/dependencies/v2"
    12  	"get.porter.sh/porter/pkg/config"
    13  	"get.porter.sh/porter/pkg/experimental"
    14  	"get.porter.sh/porter/pkg/manifest"
    15  	"get.porter.sh/porter/pkg/mixin"
    16  	"get.porter.sh/porter/pkg/tracing"
    17  	"github.com/Masterminds/semver/v3"
    18  	"github.com/cnabio/cnab-go/bundle"
    19  	"github.com/cnabio/cnab-go/bundle/definition"
    20  )
    21  
    22  // ManifestConverter converts from a porter manifest to a CNAB bundle definition.
    23  type ManifestConverter struct {
    24  	config          *config.Config
    25  	Manifest        *manifest.Manifest
    26  	ImageDigests    map[string]string
    27  	InstalledMixins []mixin.Metadata
    28  	PreserveTags    bool
    29  }
    30  
    31  func NewManifestConverter(
    32  	config *config.Config,
    33  	manifest *manifest.Manifest,
    34  	imageDigests map[string]string,
    35  	mixins []mixin.Metadata,
    36  	preserveTags bool,
    37  ) *ManifestConverter {
    38  	return &ManifestConverter{
    39  		config:          config,
    40  		Manifest:        manifest,
    41  		ImageDigests:    imageDigests,
    42  		InstalledMixins: mixins,
    43  		PreserveTags:    preserveTags,
    44  	}
    45  }
    46  
    47  func (c *ManifestConverter) ToBundle(ctx context.Context) (cnab.ExtendedBundle, error) {
    48  	ctx, span := tracing.StartSpan(ctx)
    49  	defer span.EndSpan()
    50  
    51  	stamp, err := c.GenerateStamp(ctx, c.PreserveTags)
    52  	if err != nil {
    53  		return cnab.ExtendedBundle{}, span.Error(err)
    54  	}
    55  
    56  	b := cnab.NewBundle(bundle.Bundle{
    57  		SchemaVersion: cnab.BundleSchemaVersion(),
    58  		Name:          c.Manifest.Name,
    59  		Description:   c.Manifest.Description,
    60  		Version:       c.Manifest.Version,
    61  		Maintainers:   c.generateBundleMaintainers(),
    62  		Custom:        make(map[string]interface{}, 1),
    63  	})
    64  	image := bundle.InvocationImage{
    65  		BaseImage: bundle.BaseImage{
    66  			Image:     c.Manifest.Image,
    67  			ImageType: "docker",
    68  			Digest:    c.ImageDigests[c.Manifest.Image],
    69  		},
    70  	}
    71  
    72  	b.Actions = c.generateCustomActionDefinitions()
    73  	b.Definitions = make(definition.Definitions, len(c.Manifest.Parameters)+len(c.Manifest.Outputs)+len(c.Manifest.StateBag))
    74  	b.InvocationImages = []bundle.InvocationImage{image}
    75  	b.Parameters = c.generateBundleParameters(ctx, &b.Definitions)
    76  	b.Outputs = c.generateBundleOutputs(ctx, &b.Definitions)
    77  	b.Credentials = c.generateBundleCredentials()
    78  	b.Images = c.generateBundleImages()
    79  	custom, err := c.generateCustomExtensions(ctx, &b)
    80  	if err != nil {
    81  		return cnab.ExtendedBundle{}, err
    82  	}
    83  	b.Custom = custom
    84  	b.RequiredExtensions = c.generateRequiredExtensions(b)
    85  
    86  	b.Custom[config.CustomPorterKey] = stamp
    87  
    88  	return b, nil
    89  }
    90  
    91  func (c *ManifestConverter) generateBundleMaintainers() []bundle.Maintainer {
    92  	m := make([]bundle.Maintainer, len(c.Manifest.Maintainers))
    93  	for i, item := range c.Manifest.Maintainers {
    94  		m[i] = bundle.Maintainer{
    95  			Name:  item.Name,
    96  			Email: item.Email,
    97  			URL:   item.Url,
    98  		}
    99  	}
   100  	return m
   101  }
   102  
   103  func (c *ManifestConverter) generateCustomActionDefinitions() map[string]bundle.Action {
   104  	if len(c.Manifest.CustomActions) == 0 {
   105  		return nil
   106  	}
   107  
   108  	defs := make(map[string]bundle.Action, len(c.Manifest.CustomActions))
   109  	for action, def := range c.Manifest.CustomActionDefinitions {
   110  		def := bundle.Action{
   111  			Description: def.Description,
   112  			Modifies:    def.ModifiesResources,
   113  			Stateless:   def.Stateless,
   114  		}
   115  		defs[action] = def
   116  	}
   117  
   118  	// If they used a custom action but didn't define it, default it to a safe action definition
   119  	for action := range c.Manifest.CustomActions {
   120  		if _, ok := c.Manifest.CustomActionDefinitions[action]; !ok {
   121  			defs[action] = c.generateDefaultAction(action)
   122  		}
   123  	}
   124  
   125  	return defs
   126  }
   127  
   128  func (c *ManifestConverter) generateDefaultAction(action string) bundle.Action {
   129  	// See https://github.com/cnabio/cnab-spec/blob/master/804-well-known-custom-actions.md
   130  	switch action {
   131  	case "dry-run", "io.cnab.dry-run":
   132  		return bundle.Action{
   133  			Description: "Execute the installation in a dry-run mode, allowing to see what would happen with the given set of parameter values",
   134  			Modifies:    false,
   135  			Stateless:   true,
   136  		}
   137  	case "help", "io.cnab.help":
   138  		return bundle.Action{
   139  			Description: "Print a help message to the standard output",
   140  			Modifies:    false,
   141  			Stateless:   true,
   142  		}
   143  	case "log", "io.cnab.log":
   144  		return bundle.Action{
   145  			Description: "Print logs of the installed system to the standard output",
   146  			Modifies:    false,
   147  			Stateless:   false,
   148  		}
   149  	case "status", "io.cnab.status":
   150  		return bundle.Action{
   151  			Description: "Print a human readable status message to the standard output",
   152  			Modifies:    false,
   153  			Stateless:   false,
   154  		}
   155  	case "status+json", "io.cnab.status+json":
   156  		return bundle.Action{
   157  			Description: "Print a json payload describing the detailed status with the following the CNAB status schema",
   158  			Modifies:    false,
   159  			Stateless:   false,
   160  		}
   161  	default:
   162  		// By default assume that any custom action could modify state
   163  		return bundle.Action{
   164  			Description: action,
   165  			Modifies:    true,
   166  			Stateless:   false,
   167  		}
   168  	}
   169  }
   170  
   171  func (c *ManifestConverter) generateBundleParameters(ctx context.Context, defs *definition.Definitions) map[string]bundle.Parameter {
   172  	params := make(map[string]bundle.Parameter, len(c.Manifest.Parameters))
   173  
   174  	for _, p := range c.Manifest.Parameters {
   175  		params[p.Name] = c.generateParameterDefinition(ctx, defs, p)
   176  	}
   177  
   178  	for _, p := range c.buildDefaultPorterParameters() {
   179  		params[p.Name] = c.generateParameterDefinition(ctx, defs, p)
   180  	}
   181  
   182  	return params
   183  }
   184  
   185  func (c *ManifestConverter) generateBundleOutputs(ctx context.Context, defs *definition.Definitions) map[string]bundle.Output {
   186  
   187  	outputs := make(map[string]bundle.Output, len(c.Manifest.Outputs))
   188  
   189  	for _, o := range c.Manifest.Outputs {
   190  		outputs[o.Name] = c.generateOutputDefinition(ctx, defs, o)
   191  	}
   192  
   193  	for _, o := range c.buildDefaultPorterOutputs() {
   194  		outputs[o.Name] = c.generateOutputDefinition(ctx, defs, o)
   195  	}
   196  
   197  	return outputs
   198  }
   199  
   200  func (c *ManifestConverter) generateOutputDefinition(ctx context.Context, defs *definition.Definitions,
   201  	sourceOutput manifest.OutputDefinition) bundle.Output {
   202  	log := tracing.LoggerFromContext(ctx)
   203  
   204  	destinationOutput := bundle.Output{
   205  		Definition:  sourceOutput.Name,
   206  		Description: sourceOutput.Description,
   207  		ApplyTo:     sourceOutput.ApplyTo,
   208  		// must be a standard Unix path as this will be inside of the container
   209  		// (only linux containers supported currently)
   210  		Path: path.Join(config.BundleOutputsDir, sourceOutput.Name),
   211  	}
   212  
   213  	if sourceOutput.Sensitive {
   214  		sourceOutput.Schema.WriteOnly = toBool(true)
   215  	}
   216  
   217  	if sourceOutput.Type == nil {
   218  		// Default to a file type if the param is stored in a file
   219  		if sourceOutput.Path != "" {
   220  			sourceOutput.Type = "file"
   221  		} else {
   222  			// Assume it's a string otherwise
   223  			sourceOutput.Type = "string"
   224  		}
   225  		log.Debugf("Defaulting the type of output %s to %s", sourceOutput.Name, sourceOutput.Type)
   226  	}
   227  
   228  	// Create a definition that matches the output if one isn't already defined
   229  	if _, ok := (*defs)[sourceOutput.Name]; !ok {
   230  		kind := "output"
   231  		if sourceOutput.IsState {
   232  			kind = "state"
   233  		}
   234  
   235  		defName := c.addDefinition(sourceOutput.Name, kind, sourceOutput.Schema, defs)
   236  		destinationOutput.Definition = defName
   237  	}
   238  	return destinationOutput
   239  
   240  }
   241  
   242  func (c *ManifestConverter) generateParameterDefinition(ctx context.Context, defs *definition.Definitions,
   243  	sourceParam manifest.ParameterDefinition) bundle.Parameter {
   244  	log := tracing.LoggerFromContext(ctx)
   245  
   246  	// Update ApplyTo per parameter definition and manifest
   247  	sourceParam.UpdateApplyTo(c.Manifest)
   248  
   249  	p := bundle.Parameter{
   250  		Definition:  sourceParam.Name,
   251  		ApplyTo:     sourceParam.ApplyTo,
   252  		Description: sourceParam.Description,
   253  	}
   254  
   255  	// If the default is empty, set required to true.
   256  	// State parameters are always optional, and don't have a default
   257  	if sourceParam.Default == nil && !sourceParam.IsState {
   258  		p.Required = true
   259  	}
   260  
   261  	if sourceParam.Sensitive {
   262  		sourceParam.Schema.WriteOnly = toBool(true)
   263  	}
   264  
   265  	if !sourceParam.Destination.IsEmpty() {
   266  		p.Destination = &bundle.Location{
   267  			EnvironmentVariable: sourceParam.Destination.EnvironmentVariable,
   268  			Path:                manifest.ResolvePath(sourceParam.Destination.Path),
   269  		}
   270  	} else {
   271  		p.Destination = &bundle.Location{
   272  			EnvironmentVariable: manifest.ParamToEnvVar(sourceParam.Name),
   273  		}
   274  	}
   275  
   276  	if sourceParam.Type == nil {
   277  		// Default to a file type if the param is stored in a file
   278  		if sourceParam.Destination.Path != "" {
   279  			sourceParam.Type = "file"
   280  		} else {
   281  			// Assume it's a string otherwise
   282  			sourceParam.Type = "string"
   283  		}
   284  
   285  		log.Debugf("Defaulting the type of parameter %s to %s", sourceParam.Name, sourceParam.Type)
   286  	}
   287  
   288  	// Create a definition that matches the parameter if one isn't already defined
   289  	if _, ok := (*defs)[sourceParam.Name]; !ok {
   290  		kind := "parameter"
   291  		if sourceParam.IsState {
   292  			kind = "state"
   293  		}
   294  
   295  		defName := c.addDefinition(sourceParam.Name, kind, sourceParam.Schema, defs)
   296  		p.Definition = defName
   297  	}
   298  	return p
   299  
   300  }
   301  
   302  func (c *ManifestConverter) generateCredentialDefinition(sourceCred manifest.CredentialDefinition) bundle.Credential {
   303  	destCred := bundle.Credential{
   304  		Description: sourceCred.Description,
   305  		Required:    sourceCred.Required,
   306  		// we are intentionally not mapping location and applyto
   307  		// it is not relevant to a bundle interface
   308  		// for example, they are only relevant inside the bundle
   309  		// they don't impact your ability to swap out one bundle for another
   310  	}
   311  	return destCred
   312  }
   313  
   314  func (c *ManifestConverter) addDefinition(name string, kind string, def definition.Schema, defs *definition.Definitions) string {
   315  	defName := name
   316  	if !strings.HasSuffix(name, "-"+kind) {
   317  		defName = name + "-" + kind
   318  	}
   319  
   320  	// file is a porter specific type, swap it out for something CNAB understands
   321  	if def.Type == "file" {
   322  		def.Type = "string"
   323  		def.ContentEncoding = "base64"
   324  	}
   325  
   326  	(*defs)[defName] = &def
   327  
   328  	return defName
   329  }
   330  
   331  func (c *ManifestConverter) buildDefaultPorterParameters() []manifest.ParameterDefinition {
   332  	return []manifest.ParameterDefinition{
   333  		{
   334  			Name: "porter-debug",
   335  			Destination: manifest.Location{
   336  				EnvironmentVariable: "PORTER_DEBUG",
   337  			},
   338  			Schema: definition.Schema{
   339  				ID:          "https://porter.sh/generated-bundle/#porter-debug",
   340  				Description: "Print debug information from Porter when executing the bundle",
   341  				Type:        "boolean",
   342  				Default:     false,
   343  				Comment:     cnab.PorterInternal,
   344  			},
   345  		},
   346  		{
   347  			Name:    "porter-state",
   348  			IsState: true,
   349  			Destination: manifest.Location{
   350  				Path: "/porter/state.tgz",
   351  			},
   352  			Schema: definition.Schema{
   353  				ID:              "https://porter.sh/generated-bundle/#porter-state",
   354  				Description:     "Supports persisting state for bundles. Porter internal parameter that should not be set manually.",
   355  				Type:            "string",
   356  				ContentEncoding: "base64",
   357  				Comment:         cnab.PorterInternal,
   358  			},
   359  		},
   360  	}
   361  }
   362  
   363  func (c *ManifestConverter) buildDefaultPorterOutputs() []manifest.OutputDefinition {
   364  	return []manifest.OutputDefinition{
   365  		{
   366  			Name:    "porter-state",
   367  			IsState: true,
   368  			Path:    "/cnab/app/outputs/porter-state.tgz",
   369  			Schema: definition.Schema{
   370  				ID:              "https://porter.sh/generated-bundle/#porter-state",
   371  				Description:     "Supports persisting state for bundles. Porter internal parameter that should not be set manually.",
   372  				Type:            "string",
   373  				ContentEncoding: "base64",
   374  				Comment:         cnab.PorterInternal,
   375  			},
   376  		},
   377  	}
   378  }
   379  
   380  func (c *ManifestConverter) generateBundleCredentials() map[string]bundle.Credential {
   381  	params := map[string]bundle.Credential{}
   382  	for _, cred := range c.Manifest.Credentials {
   383  		l := bundle.Credential{
   384  			Description: cred.Description,
   385  			Required:    cred.Required,
   386  			Location: bundle.Location{
   387  				Path:                manifest.ResolvePath(cred.Path),
   388  				EnvironmentVariable: cred.EnvironmentVariable,
   389  			},
   390  			ApplyTo: cred.ApplyTo,
   391  		}
   392  		params[cred.Name] = l
   393  	}
   394  	return params
   395  }
   396  
   397  func (c *ManifestConverter) generateBundleImages() map[string]bundle.Image {
   398  	images := make(map[string]bundle.Image, len(c.Manifest.ImageMap))
   399  
   400  	for i, refImage := range c.Manifest.ImageMap {
   401  		imgRefStr := refImage.Repository
   402  		if refImage.Digest != "" {
   403  			imgRefStr = fmt.Sprintf("%s@%s", imgRefStr, refImage.Digest)
   404  		} else if refImage.Tag != "" {
   405  			imgRefStr = fmt.Sprintf("%s:%s", imgRefStr, refImage.Tag)
   406  		} else { // default to `latest` if no tag is provided
   407  			imgRefStr = fmt.Sprintf("%s:latest", imgRefStr)
   408  
   409  		}
   410  		imgType := refImage.ImageType
   411  		if imgType == "" {
   412  			imgType = "docker"
   413  		}
   414  		img := bundle.Image{
   415  			Description: refImage.Description,
   416  			BaseImage: bundle.BaseImage{
   417  				Image:     imgRefStr,
   418  				Digest:    refImage.Digest,
   419  				ImageType: imgType,
   420  				MediaType: refImage.MediaType,
   421  				Size:      refImage.Size,
   422  				Labels:    refImage.Labels,
   423  			},
   424  		}
   425  		images[i] = img
   426  	}
   427  
   428  	return images
   429  }
   430  
   431  func (c *ManifestConverter) generateDependencies(ctx context.Context, defs *definition.Definitions) (interface{}, string, error) {
   432  	if len(c.Manifest.Dependencies.Requires) == 0 {
   433  		return nil, "", nil
   434  	}
   435  
   436  	// Check if they are using v1 of the dependencies spec or v2
   437  	if c.config.IsFeatureEnabled(experimental.FlagDependenciesV2) {
   438  		// Ok we are using v2!
   439  
   440  		deps, err := c.generateDependenciesV2(ctx, defs)
   441  		return deps, cnab.DependenciesV2ExtensionKey, err
   442  	}
   443  
   444  	// Default to using v1 of deps
   445  	deps := c.generateDependenciesV1(ctx)
   446  	return deps, cnab.DependenciesV1ExtensionKey, nil
   447  }
   448  
   449  func (c *ManifestConverter) generateDependenciesV1(ctx context.Context) *depsv1ext.Dependencies {
   450  	if len(c.Manifest.Dependencies.Requires) == 0 {
   451  		return nil
   452  	}
   453  
   454  	deps := &depsv1ext.Dependencies{
   455  		Sequence: make([]string, 0, len(c.Manifest.Dependencies.Requires)),
   456  		Requires: make(map[string]depsv1ext.Dependency, len(c.Manifest.Dependencies.Requires)),
   457  	}
   458  
   459  	for _, dep := range c.Manifest.Dependencies.Requires {
   460  		dependencyRef := depsv1ext.Dependency{
   461  			Name:   dep.Name,
   462  			Bundle: dep.Bundle.Reference,
   463  		}
   464  		if len(dep.Bundle.Version) > 0 {
   465  			dependencyRef.Version = &depsv1ext.DependencyVersion{
   466  				Ranges: []string{dep.Bundle.Version},
   467  			}
   468  
   469  			// If we can detect that prereleases are used in the version, then set AllowPrereleases to true
   470  			v, err := semver.NewVersion(dep.Bundle.Version)
   471  			if err == nil {
   472  				dependencyRef.Version.AllowPrereleases = v.Prerelease() != ""
   473  			}
   474  		}
   475  		deps.Sequence = append(deps.Sequence, dep.Name)
   476  		deps.Requires[dep.Name] = dependencyRef
   477  	}
   478  
   479  	return deps
   480  }
   481  
   482  func (c *ManifestConverter) generateDependenciesV2(ctx context.Context, defs *definition.Definitions) (*depsv2ext.Dependencies, error) {
   483  	deps := &depsv2ext.Dependencies{
   484  		Requires: make(map[string]depsv2ext.Dependency, len(c.Manifest.Dependencies.Requires)),
   485  	}
   486  
   487  	if c.Manifest.Dependencies.Provides != nil {
   488  		deps.Provides = &depsv2ext.DependencyProvider{
   489  			Interface: depsv2ext.InterfaceDeclaration{
   490  				ID: c.Manifest.Dependencies.Provides.Interface.ID},
   491  		}
   492  	}
   493  
   494  	for _, dep := range c.Manifest.Dependencies.Requires {
   495  		dependencyRef := depsv2ext.Dependency{
   496  			Name:    dep.Name,
   497  			Bundle:  dep.Bundle.Reference,
   498  			Version: dep.Bundle.Version,
   499  			Sharing: depsv2ext.SharingCriteria{
   500  				Mode: dep.Sharing.GetEffectiveMode(),
   501  				Group: depsv2ext.SharingGroup{
   502  					Name: dep.Sharing.Group.Name,
   503  				},
   504  			},
   505  			Parameters:  dep.Parameters,
   506  			Credentials: dep.Credentials,
   507  			Outputs:     dep.Outputs,
   508  		}
   509  
   510  		if dep.Bundle.Interface != nil {
   511  			dependencyRef.Interface = &depsv2ext.DependencyInterface{
   512  				ID:        dep.Bundle.Interface.ID,
   513  				Reference: dep.Bundle.Interface.Reference,
   514  			}
   515  
   516  			// Porter doesn't let you embed a random bundle.json document into your porter.yaml
   517  			// While the CNAB spec lets the document be anything, we constrain the interface to a porter representation of the bundle's parameters, credentials and outputs.
   518  
   519  			// TODO(PEP003): Convert the parameters, credentials and outputs defined on manifest.BundleInterfaceDocument and create an (incomplete) bundle.json from it
   520  			// See https://github.com/getporter/porter/issues/2548
   521  
   522  			if dep.Bundle.Interface.Document != nil {
   523  
   524  				depsMaps := depsv2ext.DependencyInterfaceDocument{
   525  					Outputs:     make(map[string]bundle.Output, len(dep.Bundle.Interface.Document.Outputs)),
   526  					Parameters:  make(map[string]bundle.Parameter, len(dep.Bundle.Interface.Document.Parameters)),
   527  					Credentials: make(map[string]bundle.Credential, len(dep.Bundle.Interface.Document.Credentials)),
   528  				}
   529  				dependencyRef.Interface.Document = depsMaps
   530  
   531  				for _, o := range dep.Bundle.Interface.Document.Outputs {
   532  					depsMaps.Outputs[o.Name] = c.generateOutputDefinition(ctx, defs, o)
   533  				}
   534  
   535  				for _, p := range dep.Bundle.Interface.Document.Parameters {
   536  					depsMaps.Parameters[p.Name] = c.generateParameterDefinition(ctx, defs, p)
   537  				}
   538  
   539  				for _, cred := range dep.Bundle.Interface.Document.Credentials {
   540  					depsMaps.Credentials[cred.Name] = c.generateCredentialDefinition(cred)
   541  
   542  				}
   543  			}
   544  		}
   545  
   546  		deps.Requires[dep.Name] = dependencyRef
   547  	}
   548  
   549  	return deps, nil
   550  }
   551  
   552  func (c *ManifestConverter) generateParameterSources(b *cnab.ExtendedBundle) cnab.ParameterSources {
   553  	ps := cnab.ParameterSources{}
   554  
   555  	// Parameter sources come from three places
   556  	// 1. indirectly from our template wiring
   557  	// 2. indirectly from state variables
   558  	// 3. directly when they use `source` on a parameter
   559  
   560  	// Directly wired outputs to parameters
   561  	for _, p := range c.Manifest.Parameters {
   562  		// Skip parameters that aren't set from an output
   563  		if p.Source.Output == "" {
   564  			continue
   565  		}
   566  
   567  		var pso cnab.ParameterSource
   568  		if p.Source.Dependency == "" {
   569  			pso = c.generateOutputParameterSource(p.Source.Output)
   570  		} else {
   571  			ref := manifest.DependencyOutputReference{
   572  				Dependency: p.Source.Dependency,
   573  				Output:     p.Source.Output,
   574  			}
   575  			pso = c.generateDependencyOutputParameterSource(ref)
   576  		}
   577  		ps[p.Name] = pso
   578  	}
   579  
   580  	// Directly wired state variables
   581  	// All state variables are persisted in a single file, porter-state.tgz
   582  	ps["porter-state"] = c.generateOutputParameterSource("porter-state")
   583  
   584  	// bundle.outputs.OUTPUT
   585  	for _, outputDef := range c.Manifest.GetTemplatedOutputs() {
   586  		wiringName, p, def := c.generateOutputWiringParameter(*b, outputDef.Name)
   587  		if b.Parameters == nil {
   588  			b.Parameters = make(map[string]bundle.Parameter, 1)
   589  		}
   590  		b.Parameters[wiringName] = p
   591  		b.Definitions[wiringName] = &def
   592  
   593  		pso := c.generateOutputParameterSource(outputDef.Name)
   594  		ps[wiringName] = pso
   595  	}
   596  
   597  	// bundle.dependencies.DEP.outputs.OUTPUT
   598  	for _, ref := range c.Manifest.GetTemplatedDependencyOutputs() {
   599  		wiringName, p, def := c.generateDependencyOutputWiringParameter(ref)
   600  		if b.Parameters == nil {
   601  			b.Parameters = make(map[string]bundle.Parameter, 1)
   602  		}
   603  		b.Parameters[wiringName] = p
   604  		b.Definitions[wiringName] = &def
   605  
   606  		pso := c.generateDependencyOutputParameterSource(ref)
   607  		ps[wiringName] = pso
   608  	}
   609  
   610  	return ps
   611  }
   612  
   613  // generateOutputWiringParameter creates an internal parameter used only by porter, it won't be visible to the user.
   614  // The parameter exists solely so that Porter can inject an output back into the bundle, using a parameter source.
   615  // The parameter's definition is a copy of the output's definition, with the ID set so we know that it was generated by porter.
   616  func (c *ManifestConverter) generateOutputWiringParameter(b cnab.ExtendedBundle, outputName string) (string, bundle.Parameter, definition.Schema) {
   617  	wiringName := manifest.GetParameterSourceForOutput(outputName)
   618  
   619  	paramDesc := fmt.Sprintf("Wires up the %s output for use as a parameter. Porter internal parameter that should not be set manually.", outputName)
   620  	wiringParam := c.generateWiringParameter(wiringName, paramDesc)
   621  
   622  	// Copy the output definition for use with the wiring parameter
   623  	// and identify the definition as a porter internal structure
   624  	outputDefName := b.Outputs[outputName].Definition
   625  	outputDef := b.Definitions[outputDefName]
   626  	wiringDef := *outputDef
   627  	wiringDef.ID = "https://porter.sh/generated-bundle/#porter-parameter-source-definition"
   628  	wiringDef.Comment = cnab.PorterInternal
   629  
   630  	return wiringName, wiringParam, wiringDef
   631  }
   632  
   633  // generateDependencyOutputWiringParameter creates an internal parameter used only by porter, it won't be visible to
   634  // the user. The parameter exists solely so that Porter can inject a dependency output into the bundle.
   635  func (c *ManifestConverter) generateDependencyOutputWiringParameter(reference manifest.DependencyOutputReference) (string, bundle.Parameter, definition.Schema) {
   636  	wiringName := manifest.GetParameterSourceForDependency(reference)
   637  
   638  	paramDesc := fmt.Sprintf("Wires up the %s dependency %s output for use as a parameter. Porter internal parameter that should not be set manually.", reference.Dependency, reference.Output)
   639  	wiringParam := c.generateWiringParameter(wiringName, paramDesc)
   640  
   641  	wiringDef := definition.Schema{
   642  		ID:      "https://porter.sh/generated-bundle/#porter-parameter-source-definition",
   643  		Comment: cnab.PorterInternal,
   644  		// any type, the dependency's bundle definition is not available at buildtime
   645  	}
   646  
   647  	return wiringName, wiringParam, wiringDef
   648  }
   649  
   650  // generateWiringParameter builds an internal Porter-only parameter for connecting a parameter source to a parameter.
   651  func (g *ManifestConverter) generateWiringParameter(wiringName string, description string) bundle.Parameter {
   652  	return bundle.Parameter{
   653  		Definition:  wiringName,
   654  		Description: description,
   655  		Required:    false,
   656  		Destination: &bundle.Location{
   657  			EnvironmentVariable: manifest.ParamToEnvVar(wiringName),
   658  		},
   659  	}
   660  }
   661  
   662  // generateOutputParameterSource builds a parameter source that connects a bundle output to a parameter.
   663  func (c *ManifestConverter) generateOutputParameterSource(outputName string) cnab.ParameterSource {
   664  	return cnab.ParameterSource{
   665  		Priority: []string{cnab.ParameterSourceTypeOutput},
   666  		Sources: map[string]cnab.ParameterSourceDefinition{
   667  			cnab.ParameterSourceTypeOutput: cnab.OutputParameterSource{
   668  				OutputName: outputName,
   669  			},
   670  		},
   671  	}
   672  }
   673  
   674  // generateDependencyOutputParameterSource builds a parameter source that connects a dependency output to a parameter.
   675  func (c *ManifestConverter) generateDependencyOutputParameterSource(ref manifest.DependencyOutputReference) cnab.ParameterSource {
   676  	return cnab.ParameterSource{
   677  		Priority: []string{cnab.ParameterSourceTypeDependencyOutput},
   678  		Sources: map[string]cnab.ParameterSourceDefinition{
   679  			cnab.ParameterSourceTypeDependencyOutput: cnab.DependencyOutputParameterSource{
   680  				Dependency: ref.Dependency,
   681  				OutputName: ref.Output,
   682  			},
   683  		},
   684  	}
   685  }
   686  
   687  func toBool(value bool) *bool {
   688  	return &value
   689  }
   690  
   691  func toInt(v int) *int {
   692  	return &v
   693  }
   694  
   695  func toFloat(v float64) *float64 {
   696  	return &v
   697  }
   698  
   699  func (c *ManifestConverter) generateCustomExtensions(ctx context.Context, b *cnab.ExtendedBundle) (map[string]interface{}, error) {
   700  	customExtensions := map[string]interface{}{
   701  		cnab.FileParameterExtensionKey: struct{}{},
   702  	}
   703  
   704  	// Add custom metadata defined in the manifest
   705  	for key, value := range c.Manifest.Custom {
   706  		customExtensions[key] = value
   707  	}
   708  
   709  	// Add the dependency extension
   710  	deps, depsExtKey, err := c.generateDependencies(ctx, &b.Definitions)
   711  	if err != nil {
   712  		return nil, err
   713  	}
   714  	if depsExtKey != "" {
   715  		customExtensions[depsExtKey] = deps
   716  	}
   717  
   718  	// Add the parameter sources extension
   719  	ps := c.generateParameterSources(b)
   720  	if len(ps) > 0 {
   721  		customExtensions[cnab.ParameterSourcesExtensionKey] = ps
   722  	}
   723  
   724  	// Add entries for user-specified required extensions, like docker
   725  	for _, ext := range c.Manifest.Required {
   726  		customExtensions[lookupExtensionKey(ext.Name)] = ext.Config
   727  	}
   728  
   729  	return customExtensions, nil
   730  }
   731  
   732  func (c *ManifestConverter) generateRequiredExtensions(b cnab.ExtendedBundle) []string {
   733  	requiredExtensions := []string{cnab.FileParameterExtensionKey}
   734  
   735  	// Add the appropriate dependencies key if applicable
   736  	if b.HasDependenciesV1() {
   737  		requiredExtensions = append(requiredExtensions, cnab.DependenciesV1ExtensionKey)
   738  	} else if b.HasDependenciesV2() {
   739  		requiredExtensions = append(requiredExtensions, cnab.DependenciesV2ExtensionKey)
   740  	}
   741  
   742  	// Add the appropriate parameter sources key if applicable
   743  	if b.HasParameterSources() {
   744  		requiredExtensions = append(requiredExtensions, cnab.ParameterSourcesExtensionKey)
   745  	}
   746  
   747  	// Add all under required section of manifest
   748  	for _, ext := range c.Manifest.Required {
   749  		requiredExtensions = append(requiredExtensions, lookupExtensionKey(ext.Name))
   750  	}
   751  
   752  	return requiredExtensions
   753  }
   754  
   755  // lookupExtensionKey is a helper method to return a full key matching a
   756  // supported extension, if applicable
   757  func lookupExtensionKey(name string) string {
   758  
   759  	key := name
   760  	// If an official supported extension, we grab the full key
   761  
   762  	supportedExt, err := cnab.GetSupportedExtension(name)
   763  	if err != nil {
   764  		// TODO: Issue linter warning
   765  	} else {
   766  		key = supportedExt.Key
   767  	}
   768  	return key
   769  }