From dd9877f116a12121f59303e50e073ea7083c5fd7 Mon Sep 17 00:00:00 2001 From: Rich Baird Date: Wed, 20 Jul 2022 18:25:52 -0500 Subject: [PATCH] directory parameter added. TODO: Make customizable --- pkg/cnab/config-adapter/adapter.go | 104 +++++++++++++++++++++++++++-- pkg/cnab/config-adapter/helpers.go | 7 +- pkg/cnab/directory_parameter.go | 104 +++++++++++++++++++++++++++++ pkg/cnab/extended_bundle.go | 2 + pkg/cnab/file_parameter.go | 4 +- pkg/cnab/parameter_sources.go | 17 +++++ pkg/cnab/provider/action.go | 1 - pkg/cnab/provider/driver.go | 38 +++++++++++ pkg/cnab/required.go | 1 + pkg/config/config.go | 20 +++++- pkg/manifest/helpers.go | 9 ++- pkg/manifest/manifest.go | 19 ++++-- pkg/porter/lifecycle.go | 2 + pkg/porter/parameters.go | 25 ++++--- pkg/runtime/runtime-manifest.go | 55 ++++++++++++++- 15 files changed, 373 insertions(+), 35 deletions(-) create mode 100644 pkg/cnab/directory_parameter.go diff --git a/pkg/cnab/config-adapter/adapter.go b/pkg/cnab/config-adapter/adapter.go index bae98ea7ed..5be057bbdb 100644 --- a/pkg/cnab/config-adapter/adapter.go +++ b/pkg/cnab/config-adapter/adapter.go @@ -74,7 +74,6 @@ func (c *ManifestConverter) ToBundle(ctx context.Context) (cnab.ExtendedBundle, b.Images = c.generateBundleImages() b.Custom = c.generateCustomExtensions(&b) b.RequiredExtensions = c.generateRequiredExtensions(b) - b.Custom[config.CustomPorterKey] = stamp return b, nil @@ -438,16 +437,31 @@ func (c *ManifestConverter) generateParameterSources(b *cnab.ExtendedBundle) cna // 3. directly when they use `source` on a parameter // Directly wired outputs to parameters - for _, p := range c.Manifest.Parameters { - // Skip parameters that aren't set from an output - if p.Source.Output == "" { + for k, p := range c.Manifest.Parameters { + // Skip parameters that aren't set from an output or from a directory source + if (!p.Source.IsDirSource()) && p.Source.Output == "" { continue } var pso cnab.ParameterSource - if p.Source.Dependency == "" { + if p.Source.IsDirSource() { + // If it's a directory handle it accordingly + defName := fmt.Sprintf("%s-parameter", p.Name) + pso = c.generateDirectoryParameterSource(p.Source.Mount, p.Name, p.Destination.Path) + def := c.generateDirectoryParameterSchema(*b, defName) + // Make sure that the destination is changed to an env var instead of a path + // Otherwise cnab will attempt to place the path into the container which will fail + if pb, ok := b.Parameters[k]; ok { + c.sanitizeDirParameters(pb.Destination, k) + b.Parameters[k] = pb + } + b.Definitions[defName] = &def + + } else if p.Source.Dependency == "" { + // If it's not a directory and it doesn't have a dependency, it's a standard output pso = c.generateOutputParameterSource(p.Source.Output) } else { + // Otherwise it must be a dependency ref := manifest.DependencyOutputReference{ Dependency: p.Source.Dependency, Output: p.Source.Output, @@ -490,6 +504,28 @@ func (c *ManifestConverter) generateParameterSources(b *cnab.ExtendedBundle) cna return ps } +func (c *ManifestConverter) generateDirectoryParameterSchema(b cnab.ExtendedBundle, name string) definition.Schema { + var def definition.Schema + pdef, ok := b.Definitions[name] + if ok { + MakeCNABCompatible(b.Definitions[name]) + def = *pdef + } else { + def = definition.Schema{} + def.Type = "directory" + MakeCNABCompatible(&def) + } + def.ID = "https://porter.sh/generated-bundle/#porter-parameter-source-definition" + return def +} + +// Remove the path value from directory parameters so they aren't assumed to be files +// By the cnab.io package. Apply the destination to an env var "directory-parameters.[name]" +func(c *ManifestConverter) sanitizeDirParameters(destination *bundle.Location, name string) { + destination.Path = "" + destination.EnvironmentVariable = cnab.DirectoryExtensionShortHand + "." + name +} + // generateOutputWiringParameter creates an internal parameter used only by porter, it won't be visible to the user. // The parameter exists solely so that Porter can inject an output back into the bundle, using a parameter source. // 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. @@ -552,6 +588,30 @@ func (c *ManifestConverter) generateOutputParameterSource(outputName string) cna } } +// Pass the inferred info from the parameter to the parameter source +func (c *ManifestConverter) generateDirectoryParameterSource(source interface{}, name string, target string) cnab.ParameterSource { + switch source.(type) { + case cnab.MountParameterSourceDefn: + return c.generateMountParameterSource(source.(cnab.MountParameterSourceDefn), name, target) + default: + return cnab.ParameterSource{} + } +} + +// generateMountParameterSource builds a parameter source that connects a parameter to a mount. +func (c *ManifestConverter) generateMountParameterSource(mount cnab.MountParameterSourceDefn, name string, target string) cnab.ParameterSource { + return cnab.ParameterSource{ + Priority: []string{cnab.ParameterSourceTypeMount}, + Sources: map[string]cnab.ParameterSourceDefinition{ + cnab.ParameterSourceTypeMount: func() cnab.MountParameterSourceDefn { + mount.Name = name + mount.Target = target + return mount + }(), + }, + } +} + // generateDependencyOutputParameterSource builds a parameter source that connects a dependency output to a parameter. func (c *ManifestConverter) generateDependencyOutputParameterSource(ref manifest.DependencyOutputReference) cnab.ParameterSource { return cnab.ParameterSource{ @@ -599,6 +659,13 @@ func (c *ManifestConverter) generateCustomExtensions(b *cnab.ExtendedBundle) map customExtensions[cnab.ParameterSourcesExtensionKey] = ps } + // Add the directory extension + if dirs, err := c.generateDirectoryExtension(ps); err == nil && len(dirs) > 0 { + customExtensions[cnab.DirectoryParameterExtensionKey] = dirs + } else if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + } + // Add entries for user-specified required extensions, like docker for _, ext := range c.Manifest.Required { customExtensions[lookupExtensionKey(ext.Name)] = ext.Config @@ -607,6 +674,29 @@ func (c *ManifestConverter) generateCustomExtensions(b *cnab.ExtendedBundle) map return customExtensions } +func (c *ManifestConverter) generateDirectoryExtension(ps cnab.ParameterSources) (map[string]cnab.DirectoryDetails, error) { + dirs := make(map[string]cnab.DirectoryDetails, 0) + for name, param := range ps { + for _, src := range param.Sources { + switch src.(type) { + case cnab.MountParameterSourceDefn: + dirs[name] = cnab.DirectoryDetails{ + DirectorySources: cnab.DirectorySources{ + Mount: src.(cnab.MountParameterSourceDefn), + }, + DirectoryParameterDefinition: c.Manifest.Parameters[name].DirectoryParameterDefinition, + Kind: cnab.ParameterSourceTypeMount, + } + break + default: + continue + } + } + } + + return dirs, nil +} + func (c *ManifestConverter) generateRequiredExtensions(b cnab.ExtendedBundle) []string { requiredExtensions := []string{cnab.FileParameterExtensionKey} @@ -620,6 +710,10 @@ func (c *ManifestConverter) generateRequiredExtensions(b cnab.ExtendedBundle) [] requiredExtensions = append(requiredExtensions, cnab.ParameterSourcesExtensionKey) } + if b.HasDirectoryParameters() { + requiredExtensions = append(requiredExtensions, cnab.DirectoryParameterExtensionKey) + } + // Add all under required section of manifest for _, ext := range c.Manifest.Required { requiredExtensions = append(requiredExtensions, lookupExtensionKey(ext.Name)) diff --git a/pkg/cnab/config-adapter/helpers.go b/pkg/cnab/config-adapter/helpers.go index cf48acc2af..f0fa18dce4 100644 --- a/pkg/cnab/config-adapter/helpers.go +++ b/pkg/cnab/config-adapter/helpers.go @@ -22,9 +22,10 @@ func ConvertToTestBundle(ctx context.Context, cfg *config.Config, manifest *mani // Returns true if values were replaced and false otherwise. func MakeCNABCompatible(schema *definition.Schema) bool { if v, ok := schema.Type.(string); ok { - if t, ok := config.PorterParamMap[v]; ok { - schema.Type = t - schema.ContentEncoding = "base64" + if c, ok := config.PorterParamMap[v]; ok { + schema.Type = c.Type + schema.ContentEncoding = c.Encoding + schema.Comment = c.Comment return ok } } diff --git a/pkg/cnab/directory_parameter.go b/pkg/cnab/directory_parameter.go new file mode 100644 index 0000000000..2403903797 --- /dev/null +++ b/pkg/cnab/directory_parameter.go @@ -0,0 +1,104 @@ +package cnab + +import ( + "encoding/json" + + "github.com/cnabio/cnab-go/bundle/definition" + "github.com/docker/docker/api/types/mount" + "github.com/pkg/errors" +) + +const ( + DirectoryExtensionShortHand = "directory-parameter" + DirectoryParameterExtensionKey = PorterExtensionsPrefix + DirectoryExtensionShortHand +) + +// DirectoryParameterDefinition represents those parameter options +// That apply exclusively to the directory parameter type +type DirectoryParameterDefinition struct { + Writeable bool `yaml:"writeable,omitempty"` + // UID and GID should be ints, however 0 is the default value for int type + // But is also a realistic value for UID/GID thus we need to make the type interface + // To detect the case that the values weren't set + GID interface{} `yaml:"gid,omitempty" json:"gid,omitempty"` + UID interface{} `yaml:"uid,omitempty" json:"uid,omitempty"` +} + +// MountParameterSource represents a parameter using a docker mount +// As a its source with the provided options +type MountParameterSourceDefn struct { + mount.Mount `yaml:",inline"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` +} + +// DirectorySources represents the sources available to the directory parameter type +// Currently only mount has been specified, but this could change in the future +type DirectorySources struct { + Mount MountParameterSourceDefn `yaml:"mount,omitempty" json:"mount,omitempty"` +} +type DirectoryDetails struct { + DirectorySources + DirectoryParameterDefinition + Kind string `json:"kind,omitempty"` +} + +// DirectoryParameterExtension indicates that Directory support is required +var DirectoryParameterExtension = RequiredExtension{ + Shorthand: DirectoryExtensionShortHand, + Key: DirectoryParameterExtensionKey, + Reader: DirectoryParameterReader, +} + +// SupportsDirectoryParameters returns true if the bundle supports the +// Directory parameter extension +func (b ExtendedBundle) SupportsDirectoryParameters() bool { + return b.SupportsExtension(DirectoryParameterExtensionKey) +} + +// IsDirType determines if the parameter/credential is of type "directory". +func (b ExtendedBundle) IsDirType(def *definition.Schema) bool { + return b.SupportsDirectoryParameters() && def.Type == "string" && def.Comment == DirectoryParameterExtensionKey +} + +// DirectoryParameterReader is a Reader for the DirectoryParameterExtension. +// The extension maintains the list of directory parameters in the bundle +func DirectoryParameterReader(b ExtendedBundle) (interface{}, error) { + return b.DirectoryParameterReader() +} + +// DirectoryParameterReader is a Reader for the DirectoryParameterExtension. +// This method generates the list of directory parameter names in the bundle. +// The Directory Parameter extension maintains the list of directory parameters in the bundle +func (b ExtendedBundle) DirectoryParameterReader() (interface{}, error) { + bytes, err := json.Marshal(b.Custom[DirectoryParameterExtensionKey]) + if err != nil { + return nil, errors.Wrapf(err, "Failed to marshal custom extension %s", DirectoryParameterExtensionKey) + } + var dd map[string]DirectoryDetails + if err = errors.Wrapf(json.Unmarshal(bytes, &dd), "Failed to unmarshal custom extension %s %s", DirectoryParameterExtensionKey, string(bytes)); err != nil { + return nil, err + } + dirs := make([]DirectoryDetails, len(dd)) + i := 0 + for _, dir := range dd { + dirs[i] = dir + i++ + } + return dirs, nil +} + +// DirectoryParameterSupport checks if the Directory parameter extension is present +func (e ProcessedExtensions) DirectoryParameterSupport() bool { + _, extensionRequired := e[DirectoryParameterExtensionKey] + return extensionRequired +} + +// IDToInt converts an interface to an integer. If the id is coercable to an int, returns the value +// Otherwise returns -1 +func IDToInt(id interface{}) int { + if i, ok := id.(int); ok { + return i + } + + return -1 +} diff --git a/pkg/cnab/extended_bundle.go b/pkg/cnab/extended_bundle.go index 16f45de70e..821476993d 100644 --- a/pkg/cnab/extended_bundle.go +++ b/pkg/cnab/extended_bundle.go @@ -84,6 +84,8 @@ func (b ExtendedBundle) GetParameterType(def *definition.Schema) string { return fmt.Sprintf("%v", def.Type) } + + // IsFileType determines if the parameter/credential is of type "file". func (b ExtendedBundle) IsFileType(def *definition.Schema) bool { return b.SupportsFileParameters() && diff --git a/pkg/cnab/file_parameter.go b/pkg/cnab/file_parameter.go index cd3fc1b5b3..65c025da01 100644 --- a/pkg/cnab/file_parameter.go +++ b/pkg/cnab/file_parameter.go @@ -16,13 +16,13 @@ var FileParameterExtension = RequiredExtension{ Reader: FileParameterReader, } + // FileParameterReader is a Reader for the FileParameterExtension. // The extension does not have any data, its presence indicates that // parameters of type "file" should be supported by the tooling. func FileParameterReader(b ExtendedBundle) (interface{}, error) { return b.FileParameterReader() } - // FileParameterReader is a Reader for the FileParameterExtension. // The extension does not have any data, its presence indicates that // parameters of type "file" should be supported by the tooling. @@ -45,4 +45,4 @@ func (b ExtendedBundle) SupportsFileParameters() bool { func (e ProcessedExtensions) FileParameterSupport() bool { _, extensionRequired := e[FileParameterExtensionKey] return extensionRequired -} +} \ No newline at end of file diff --git a/pkg/cnab/parameter_sources.go b/pkg/cnab/parameter_sources.go index cf50d5db39..58d301526c 100644 --- a/pkg/cnab/parameter_sources.go +++ b/pkg/cnab/parameter_sources.go @@ -20,6 +20,9 @@ const ( // ParameterSourceTypeDependencyOutput defines a type of parameter source that is provided by a bundle's dependency // output. ParameterSourceTypeDependencyOutput = "dependencies.output" + + // ParameterSouceTypeMount defines a type of parameter source that is provided by a docker mount + ParameterSourceTypeMount = "docker.mount" ) // ParameterSourcesExtension represents a required extension that specifies how @@ -127,6 +130,14 @@ func (m *ParameterSourceMap) UnmarshalJSON(data []byte) error { return errors.Wrapf(err, "invalid parameter source definition for key %s", sourceKey) } (*m)[ParameterSourceTypeDependencyOutput] = depOutput + case ParameterSourceTypeMount: + var src MountParameterSourceDefn + err := json.Unmarshal(rawDef, &src) + if err != nil { + return errors.Wrapf(err, "invalid parameter source definition for key %s", sourceKey) + } + (*m)[ParameterSourceTypeMount] = src + default: return errors.Errorf("unsupported parameter source key %s", sourceKey) } @@ -224,6 +235,12 @@ func (b ExtendedBundle) HasParameterSources() bool { return ok } +// HasDirectoryParameters returns whether or not the bundle has directory parameters defined. +func (b ExtendedBundle) HasDirectoryParameters() bool { + _, ok := b.Custom[DirectoryParameterExtensionKey] + return ok +} + // ParameterHasSource determines if the specified parameter has a parameter // source defined. func (b ExtendedBundle) ParameterHasSource(paramName string) bool { diff --git a/pkg/cnab/provider/action.go b/pkg/cnab/provider/action.go index ddf4c017d0..29fb54d8c3 100644 --- a/pkg/cnab/provider/action.go +++ b/pkg/cnab/provider/action.go @@ -172,7 +172,6 @@ func (r *Runtime) Execute(ctx context.Context, args ActionArguments) error { } r.printDebugInfo(b, creds, args.Params) - opResult, result, err := a.Run(currentRun.ToCNAB(), creds.ToCNAB(), r.ApplyConfig(ctx, args)...) if currentRun.ShouldRecord() { diff --git a/pkg/cnab/provider/driver.go b/pkg/cnab/provider/driver.go index 5d9a2f14ee..2bd32deb08 100644 --- a/pkg/cnab/provider/driver.go +++ b/pkg/cnab/provider/driver.go @@ -1,11 +1,15 @@ package cnabprovider import ( + "os" + "strings" + "get.porter.sh/porter/pkg/cnab" "get.porter.sh/porter/pkg/cnab/drivers" "github.com/cnabio/cnab-go/driver" "github.com/cnabio/cnab-go/driver/docker" "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/mount" "github.com/pkg/errors" ) @@ -44,6 +48,40 @@ func (r *Runtime) newDriver(driverName string, args ActionArguments) (driver.Dri return nil, err } + // Handle Directory support for the docker driver + // TODO: Handle directory support for other runtimes -- how? + if driverName == "docker" && r.Extensions.DirectoryParameterSupport() { + d := driverImpl.(*docker.Driver) + for _, dd := range r.Extensions[cnab.DirectoryParameterExtension.Key].([]cnab.DirectoryDetails) { + switch dd.Kind { + case cnab.ParameterSourceTypeMount: + d.AddConfigurationOptions(func(cfg *container.Config, hostCfg *container.HostConfig) error { + x := dd.Mount + x.Type = "bind" + x.ReadOnly = !dd.Writeable + pairs := make([]string, len(os.Environ())*2) + for i, env := range os.Environ() { + parts := strings.Split(env, "=") + pairs[i*2] = "$" + parts[0] + pairs[i*2+1] = parts[1] + } + + rep := strings.NewReplacer(pairs...) + x.Source = rep.Replace(x.Source) + x.Target = rep.Replace(x.Target) + if hostCfg.Mounts == nil || len(hostCfg.Mounts) < 1 { + hostCfg.Mounts = []mount.Mount{ + x.Mount, + } + } else { + hostCfg.Mounts = append(hostCfg.Mounts, x.Mount) + } + return nil + }) + } + } + } + if configurable, ok := driverImpl.(driver.Configurable); ok { driverCfg := make(map[string]string) // Load any driver-specific config out of the environment diff --git a/pkg/cnab/required.go b/pkg/cnab/required.go index b9a15bac92..b5612b08b8 100644 --- a/pkg/cnab/required.go +++ b/pkg/cnab/required.go @@ -19,6 +19,7 @@ var SupportedExtensions = []RequiredExtension{ DockerExtension, FileParameterExtension, ParameterSourcesExtension, + DirectoryParameterExtension, } // ProcessedExtensions represents a map of the extension name to the diff --git a/pkg/config/config.go b/pkg/config/config.go index 12ff74044b..2e9a393686 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -56,10 +56,24 @@ const ( EnvPorterInstallationName = "PORTER_INSTALLATION_NAME" ) +type CustomParam struct { + Type string + Encoding string + Comment string +} + // PorterParamMap maps custom porter parameter types to a CNAB compatible alternative -var PorterParamMap = map[string]string { - "file": "string", - "directory": "string", +// Comment is specified to indicate the original type, which is required to differentiate +// between multiple custom param types +var PorterParamMap = map[string]CustomParam { + "file": { + Type: "string", + Encoding: "base64", + }, + "directory": { + Type: "string", + Comment: fmt.Sprintf("%s.directory-parameter", CustomPorterKey), + }, } diff --git a/pkg/manifest/helpers.go b/pkg/manifest/helpers.go index 49c1944edd..cb8c40bab3 100644 --- a/pkg/manifest/helpers.go +++ b/pkg/manifest/helpers.go @@ -2,15 +2,14 @@ package manifest import "get.porter.sh/porter/pkg/config" -// MakeCNABCompatible receives a schema with possible porter specific parameters +// MakeCNABCompatible receives a Paramaeter Definition with possible porter specific parameters // and converts those parameters to CNAB compatible versions. // Returns true if values were replaced and false otherwise. func MakeCNABCompatible(def *ParameterDefinition) bool { if v, ok := def.Type.(string); ok { - if t, ok := config.PorterParamMap[v]; ok { - def.Type = t - def.ContentEncoding = "base64" - + if c, ok := config.PorterParamMap[v]; ok { + def.Type = c.Type + def.ContentEncoding = c.Encoding return ok } } diff --git a/pkg/manifest/manifest.go b/pkg/manifest/manifest.go index bfcbb5f0a9..be306eed17 100644 --- a/pkg/manifest/manifest.go +++ b/pkg/manifest/manifest.go @@ -320,11 +320,14 @@ func (pd *ParameterDefinitions) UnmarshalYAML(unmarshal func(interface{}) error) var _ bundle.Scoped = &ParameterDefinition{} + + // ParameterDefinition defines a single parameter for a CNAB bundle type ParameterDefinition struct { - Name string `yaml:"name"` - Sensitive bool `yaml:"sensitive"` - Source ParameterSource `yaml:"source,omitempty"` + cnab.DirectoryParameterDefinition `yaml:",inline"` + Name string `yaml:"name"` + Sensitive bool `yaml:"sensitive"` + Source ParameterSource `yaml:"source,omitempty"` // These fields represent a subset of bundle.Parameter as defined in cnabio/cnab-go, // minus the 'Description' field (definition.Schema's will be used) and `Definition` field @@ -423,8 +426,9 @@ func (pd *ParameterDefinition) UpdateApplyTo(m *Manifest) { } type ParameterSource struct { - Dependency string `yaml:"dependency,omitempty"` - Output string `yaml:"output"` + cnab.DirectorySources `yaml:",inline"` + Dependency string `yaml:"dependency,omitempty"` + Output string `yaml:"output"` } // CredentialDefinitions allows us to represent credentials as a list in the YAML @@ -441,6 +445,11 @@ func (cd CredentialDefinitions) MarshalYAML() (interface{}, error) { return raw, nil } +// IsDirSource returns true if the Parameter Source is a Directory Source +func (p *ParameterSource) IsDirSource() bool { + return reflect.Indirect(reflect.ValueOf(p)).Kind() == reflect.ValueOf(cnab.DirectorySources{}).Kind() +} + func (cd *CredentialDefinitions) UnmarshalYAML(unmarshal func(interface{}) error) error { var raw []CredentialDefinition err := unmarshal(&raw) diff --git a/pkg/porter/lifecycle.go b/pkg/porter/lifecycle.go index edfbfde58d..812ca28380 100644 --- a/pkg/porter/lifecycle.go +++ b/pkg/porter/lifecycle.go @@ -202,6 +202,8 @@ func (p *Porter) BuildActionArgs(ctx context.Context, installation storage.Insta return cnabprovider.ActionArguments{}, err } + + args := cnabprovider.ActionArguments{ Action: action.GetAction(), Installation: installation, diff --git a/pkg/porter/parameters.go b/pkg/porter/parameters.go index 218944f651..5351cf1df1 100644 --- a/pkg/porter/parameters.go +++ b/pkg/porter/parameters.go @@ -638,6 +638,7 @@ func (p *Porter) resolveParameterSources(ctx context.Context, bun cnab.ExtendedB for _, rawSource := range parameterSource.ListSourcesByPriority() { var installationName string var outputName string + var mount *cnab.MountParameterSourceDefn switch source := rawSource.(type) { case cnab.OutputParameterSource: installationName = installation.Name @@ -646,19 +647,18 @@ func (p *Porter) resolveParameterSources(ctx context.Context, bun cnab.ExtendedB // TODO(carolynvs): does this need to take namespace into account installationName = cnab.BuildPrerequisiteInstallationName(installation.Name, source.Dependency) outputName = source.OutputName + case cnab.MountParameterSourceDefn: + installationName = installation.Name + mount = &source } output, err := p.Installations.GetLastOutput(ctx, installation.Namespace, installationName, outputName) if err != nil { - // When we can't find the output, skip it and let the parameter be set another way - if errors.Is(err, storage.ErrNotFound{}) { - if p.Debug { - fmt.Fprintf(p.Err, "No previous output found for %s from %s/%s\n", outputName, installation.Namespace, installationName) - } - continue + // When we can't find the output, it may be a directory parameter, or we may have to find it some other way + if !errors.Is(err, storage.ErrNotFound{}) { + // Otherwise, something else has happened, perhaps bad data or connectivity problems, we can't ignore it + return nil, errors.Wrapf(err, "could not set parameter %s from output %s of %s", parameterName, outputName, installation) } - // Otherwise, something else has happened, perhaps bad data or connectivity problems, we can't ignore it - return nil, errors.Wrapf(err, "could not set parameter %s from output %s of %s", parameterName, outputName, installation) } if output.Key != "" { @@ -682,6 +682,15 @@ func (p *Porter) resolveParameterSources(ctx context.Context, bun cnab.ExtendedB if bun.IsFileType(def) { values[parameterName] = base64.StdEncoding.EncodeToString(output.Value) + } else if bun.IsDirType(def) && mount != nil { + p := bun.Parameters[parameterName] + // p.Definition = config.CustomPorterKey + ".directory" + bun.Parameters[parameterName] = p + if bytes, err := json.Marshal(mount); err == nil { + values[parameterName] = string(bytes) + } else { + return nil, fmt.Errorf("Could not marshal source for definition %s", param.Definition) + } } else { values[parameterName] = string(output.Value) } diff --git a/pkg/runtime/runtime-manifest.go b/pkg/runtime/runtime-manifest.go index 2bd8cb40cd..ed13d6cc0b 100644 --- a/pkg/runtime/runtime-manifest.go +++ b/pkg/runtime/runtime-manifest.go @@ -12,7 +12,7 @@ import ( "strings" "get.porter.sh/porter/pkg" - "get.porter.sh/porter/pkg/cnab" + cnab "get.porter.sh/porter/pkg/cnab" "get.porter.sh/porter/pkg/config" "get.porter.sh/porter/pkg/manifest" "get.porter.sh/porter/pkg/portercontext" @@ -115,7 +115,20 @@ func (m *RuntimeManifest) loadDependencyDefinitions() error { return nil } -func (m *RuntimeManifest) resolveParameter(pd manifest.ParameterDefinition) string { +func (m *RuntimeManifest) resolveParameter(pd manifest.ParameterDefinition) interface{} { + // Handle directories separately + if pd.Schema.Comment == cnab.DirectoryParameterExtensionKey { + if pd.Source.Mount.Source != "" { + pd.Source.Mount.Target = pd.Destination.Path + } + return map[string]interface{}{ + "path": pd.Destination.Path, + "uid": cnab.IDToInt(pd.UID), + "gid": cnab.IDToInt(pd.GID), + "writeable": pd.Writeable, + "source": pd.Source, + } + } if pd.Destination.EnvironmentVariable != "" { return m.Getenv(pd.Destination.EnvironmentVariable) } @@ -154,6 +167,27 @@ func (m *RuntimeManifest) GetSensitiveValues() []string { return m.sensitiveValues } +// setRecursiveSensitiveValue handles the case of a sensitive parameter being a map of strings +// of arbitrary depth +func (m *RuntimeManifest) setRecursiveSensitiveValue(v interface{}, key string) error { + var ok bool = true + for ok { + v, ok = v.(map[string]interface{}) + for k, i := range v.(map[string]interface{}) { + key += "." + k + m.setRecursiveSensitiveValue(i, key) + } + } + + if s, ok := v.(string); ok { + m.setSensitiveValue(s) + return nil + } + + return errors.Wrapf(errors.New("Conversion Error"), "Could not convert %s to string", key) + +} + func (m *RuntimeManifest) setSensitiveValue(val string) { exists := false for _, item := range m.sensitiveValues { @@ -256,7 +290,14 @@ func (m *RuntimeManifest) buildSourceData() (map[string]interface{}, error) { pe := param.Name val := m.resolveParameter(param) if param.Sensitive { - m.setSensitiveValue(val) + if val, ok := val.(string); ok { + m.setSensitiveValue(val) + } else { + err := m.setRecursiveSensitiveValue(val, "parameters") + if err != nil { + return nil, err + } + } } params[pe] = val } @@ -458,6 +499,14 @@ func (m *RuntimeManifest) Initialize() error { continue } + if m.bundle.IsDirType(def) { + // Update the manifest schema so we can detect directory type + // when we inject parameter values + p := m.Parameters[paramName] + p.Schema = *def + m.Parameters[paramName] = p + } + if m.bundle.IsFileType(def) { if param.Destination.Path == "" { return fmt.Errorf("destination path is not supplied for parameter %s", paramName)