Skip to content

Commit

Permalink
directory parameter added. TODO: Make customizable
Browse files Browse the repository at this point in the history
Signed-off-by: Rich Baird <Rich@rbaird.me>
  • Loading branch information
Rich Baird authored and Rich Baird committed Jul 25, 2022
1 parent ee52834 commit b1b664e
Show file tree
Hide file tree
Showing 15 changed files with 373 additions and 35 deletions.
104 changes: 99 additions & 5 deletions pkg/cnab/config-adapter/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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
Expand All @@ -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}

Expand All @@ -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))
Expand Down
7 changes: 4 additions & 3 deletions pkg/cnab/config-adapter/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down
104 changes: 104 additions & 0 deletions pkg/cnab/directory_parameter.go
Original file line number Diff line number Diff line change
@@ -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
}
2 changes: 2 additions & 0 deletions pkg/cnab/extended_bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() &&
Expand Down
4 changes: 2 additions & 2 deletions pkg/cnab/file_parameter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -45,4 +45,4 @@ func (b ExtendedBundle) SupportsFileParameters() bool {
func (e ProcessedExtensions) FileParameterSupport() bool {
_, extensionRequired := e[FileParameterExtensionKey]
return extensionRequired
}
}
17 changes: 17 additions & 0 deletions pkg/cnab/parameter_sources.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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 {
Expand Down
1 change: 0 additions & 1 deletion pkg/cnab/provider/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Loading

0 comments on commit b1b664e

Please sign in to comment.