diff --git a/go1.18.3.linux-amd64.tar.gz b/go1.18.3.linux-amd64.tar.gz new file mode 100644 index 0000000000..b830f85f57 Binary files /dev/null and b/go1.18.3.linux-amd64.tar.gz differ diff --git a/pkg/cnab/config-adapter/adapter.go b/pkg/cnab/config-adapter/adapter.go index 32c281222b..bae98ea7ed 100644 --- a/pkg/cnab/config-adapter/adapter.go +++ b/pkg/cnab/config-adapter/adapter.go @@ -3,6 +3,7 @@ package configadapter import ( "context" "fmt" + "os" "path" "strings" @@ -196,8 +197,11 @@ func (c *ManifestConverter) generateBundleParameters(ctx context.Context, defs * } if param.Type == nil { - // Default to a file type if the param is stored in a file - if param.Destination.Path != "" { + // Default to a directory type if the param is a directory + if param.Destination.Path != "" && strings.HasSuffix(param.Destination.Path, string(os.PathSeparator)) { + param.Type = "directory" + } else if param.Destination.Path != "" { + // If the path could refer to a file assume that it does unless specified explicity param.Type = "file" } else { // Assume it's a string otherwise @@ -291,12 +295,8 @@ func (c *ManifestConverter) addDefinition(name string, kind string, def definiti defName = name + "-" + kind } - // file is a porter specific type, swap it out for something CNAB understands - if def.Type == "file" { - def.Type = "string" - def.ContentEncoding = "base64" - } - + // Type may be a porter specific type, swap it out for something CNAB understands + MakeCNABCompatible(&def) (*defs)[defName] = &def return defName diff --git a/pkg/cnab/config-adapter/helpers.go b/pkg/cnab/config-adapter/helpers.go index 318257f964..404c87a2b1 100644 --- a/pkg/cnab/config-adapter/helpers.go +++ b/pkg/cnab/config-adapter/helpers.go @@ -6,6 +6,7 @@ import ( "get.porter.sh/porter/pkg/cnab" "get.porter.sh/porter/pkg/config" "get.porter.sh/porter/pkg/manifest" + "github.com/cnabio/cnab-go/bundle/definition" ) // ConvertToTestBundle is suitable for taking a test manifest (porter.yaml) @@ -15,3 +16,13 @@ func ConvertToTestBundle(ctx context.Context, cfg *config.Config, manifest *mani converter := NewManifestConverter(cfg, manifest, nil, nil) return converter.ToBundle(ctx) } + + +func MakeCNABCompatible(schema *definition.Schema) { + if v, ok := schema.Type.(string); ok { + if t, ok := config.PorterParamMap[v]; ok { + schema.Type = t; + schema.ContentEncoding = "base64" + } + } +} \ No newline at end of file diff --git a/pkg/config/config.go b/pkg/config/config.go index 5b53b4935d..12ff74044b 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -56,6 +56,13 @@ const ( EnvPorterInstallationName = "PORTER_INSTALLATION_NAME" ) +// PorterParamMap maps custom porter parameter types to a CNAB compatible alternative +var PorterParamMap = map[string]string { + "file": "string", + "directory": "string", +} + + // These are functions that afero doesn't support, so this lets us stub them out for tests to set the // location of the current executable porter binary and resolve PORTER_HOME. var getExecutable = os.Executable diff --git a/pkg/manifest/helpers.go b/pkg/manifest/helpers.go new file mode 100644 index 0000000000..7742e8b427 --- /dev/null +++ b/pkg/manifest/helpers.go @@ -0,0 +1,16 @@ +package manifest + +import "get.porter.sh/porter/pkg/config" + +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" + + return ok + } + } + + return false +} diff --git a/pkg/manifest/manifest.go b/pkg/manifest/manifest.go index 732246041d..bfcbb5f0a9 100644 --- a/pkg/manifest/manifest.go +++ b/pkg/manifest/manifest.go @@ -348,11 +348,13 @@ func (pd *ParameterDefinition) Validate() error { result = multierror.Append(result, errors.New("parameter name is required")) } - // Porter supports declaring a parameter of type: "file", + // Porter supports declaring a parameter of types: "file" and "directory", // which we will convert to the appropriate bundle.Parameter type in adapter.go // Here, we copy the ParameterDefinition and make the same modification before validation pdCopy := pd.DeepCopy() - if pdCopy.Type == "file" { + if MakeCNABCompatible(pdCopy) { + // TODO: Currently all custom parameter types require a path property. This may not be the case in the future, + // Instead, we should do the validation separately for each type. if pd.Destination.Path == "" { result = multierror.Append(result, fmt.Errorf("no destination path supplied for parameter %s", pd.Name)) } diff --git a/scratch.go b/scratch.go new file mode 100644 index 0000000000..fbde42352d --- /dev/null +++ b/scratch.go @@ -0,0 +1,242 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" +) + +// Type represents the type of a mount. +type Type string + +// Type constants +const ( + // TypeBind is the type for mounting host dir + TypeBind Type = "bind" + // TypeVolume is the type for remote storage volumes + TypeVolume Type = "volume" + // TypeTmpfs is the type for mounting tmpfs + TypeTmpfs Type = "tmpfs" + // TypeNamedPipe is the type for mounting Windows named pipes + TypeNamedPipe Type = "npipe" +) + +// Mount represents a mount (volume). +type Mount struct { + Type Type `json:""` + // Source specifies the name of the mount. Depending on mount type, this + // may be a volume name or a host path, or even ignored. + // Source is not supported for tmpfs (must be an empty value) + Source string `json:""` + Target string `json:""` + ReadOnly bool `json:""` + Consistency Consistency `json:""` + + BindOptions *BindOptions `json:""` + VolumeOptions *VolumeOptions `json:""` + TmpfsOptions *TmpfsOptions `json:""` +} + +// Propagation represents the propagation of a mount. +type Propagation string + +const ( + // PropagationRPrivate RPRIVATE + PropagationRPrivate Propagation = "rprivate" + // PropagationPrivate PRIVATE + PropagationPrivate Propagation = "private" + // PropagationRShared RSHARED + PropagationRShared Propagation = "rshared" + // PropagationShared SHARED + PropagationShared Propagation = "shared" + // PropagationRSlave RSLAVE + PropagationRSlave Propagation = "rslave" + // PropagationSlave SLAVE + PropagationSlave Propagation = "slave" +) + +// Propagations is the list of all valid mount propagations +var Propagations = []Propagation{ + PropagationRPrivate, + PropagationPrivate, + PropagationRShared, + PropagationShared, + PropagationRSlave, + PropagationSlave, +} + +// Consistency represents the consistency requirements of a mount. +type Consistency string + +const ( + // ConsistencyFull guarantees bind mount-like consistency + ConsistencyFull Consistency = "consistent" + // ConsistencyCached mounts can cache read data and FS structure + ConsistencyCached Consistency = "cached" + // ConsistencyDelegated mounts can cache read and written data and structure + ConsistencyDelegated Consistency = "delegated" + // ConsistencyDefault provides "consistent" behavior unless overridden + ConsistencyDefault Consistency = "default" +) + +// BindOptions defines options specific to mounts of type "bind". +type BindOptions struct { + Propagation Propagation `json:""` + NonRecursive bool `json:""` +} + +// VolumeOptions represents the options for a mount of type volume. +type VolumeOptions struct { + NoCopy bool `json:""` + Labels map[string]string `json:""` + DriverConfig *Driver `json:""` +} + +// Driver represents a volume driver. +type Driver struct { + Name string `json:""` + Options map[string]string `json:""` +} + +// TmpfsOptions defines options specific to mounts of type "tmpfs". +type TmpfsOptions struct { + // Size sets the size of the tmpfs, in bytes. + // + // This will be converted to an operating system specific value + // depending on the host. For example, on linux, it will be converted to + // use a 'k', 'm' or 'g' syntax. BSD, though not widely supported with + // docker, uses a straight byte value. + // + // Percentages are not supported. + SizeBytes int64 `json:""` + // Mode of the tmpfs upon creation + Mode os.FileMode `json:""` + + // TODO(stevvooe): There are several more tmpfs flags, specified in the + // daemon, that are accepted. Only the most basic are added for now. + // + // From https://github.com/moby/sys/blob/mount/v0.1.1/mount/flags.go#L47-L56 + // + // var validFlags = map[string]bool{ + // "": true, + // "size": true, X + // "mode": true, X + // "uid": true, + // "gid": true, + // "nr_inodes": true, + // "nr_blocks": true, + // "mpol": true, + // } + // + // Some of these may be straightforward to add, but others, such as + // uid/gid have implications in a clustered system. +} + +type PortBinding struct { + // HostIP is the host IP Address + HostIP string `json:"HostIp"` + // HostPort is the host port number + HostPort string +} + +// PortMap is a collection of PortBinding indexed by Port +type PortMap map[Port][]PortBinding + +// PortSet is a collection of structs indexed by Port +type PortSet map[Port]struct{} + +// Port is a string containing port number and protocol in the format "80/tcp" +type Port string + +// RestartPolicy represents the restart policies of the container. +type RestartPolicy struct { + Name string + MaximumRetryCount int +} + +// Docker describes the set of custom extension metadata associated with the Docker extension +type Docker struct { + // Privileged represents whether or not the Docker container should run as --privileged + Privileged bool `json:"privileged"` + // Mounts represent mounts to be attached to the host machine with all configurable options. + Mounts []Mount `json:"mounts"` + // Network represents the network type applied to the container "host,bridged,etc" + Network string `json:"network"` + // CapAdd represents the capabilities available to the container kernel + CapAdd []string `json:"capadd"` + // CapDrop represents capabilities to exclude from the container kernel + CapDrop []string `json:"capdrop"` + // Ports to bind between the host and the container + PortBindings []PortMap `json:"portBindings"` + // Restart policy to be used for the container + // This may be useful in some rare cases + RestartPolicy RestartPolicy `json:"restartPolicy"` +} + +func PrettyStruct(data interface{}) (string, error) { + val, err := json.MarshalIndent(data, "", " ") + if err != nil { + return "", err + } + return string(val), nil +} + +func main() { + docker := Docker{ + Privileged: false, + Mounts: []Mount{ + { + Type: "", + Source: "", + Target: "", + ReadOnly: false, + Consistency: "", + BindOptions: &BindOptions{ + Propagation: "", + NonRecursive: false, + }, + VolumeOptions: &VolumeOptions{ + NoCopy: false, + Labels: map[string]string{ + "": "", + }, + DriverConfig: &Driver{ + Name: "", + Options: map[string]string{ + "": "", + }, + }, + }, + TmpfsOptions: &TmpfsOptions{ + SizeBytes: 0, + Mode: 0, + }, + }, + }, + Network: "", + CapAdd: []string{ + "c1", + "c2", + }, + CapDrop: []string{ + "c3", + "c4", + }, + PortBindings: []PortMap{ + { + "80": []PortBinding{ + { + HostIP: "0.0.0.0", + HostPort: "8080", + }, + }, + }, + }, + RestartPolicy: RestartPolicy{ + Name: "", + MaximumRetryCount: 0, + }, + } + + fmt.Println(PrettyStruct(docker)) +} diff --git a/scratch.json b/scratch.json new file mode 100644 index 0000000000..6e646478f8 --- /dev/null +++ b/scratch.json @@ -0,0 +1,58 @@ +{ + "required": + { + "docker": { + "privileged": false, + "mounts": [ + { + "Type": "", + "Source": "", + "Target": "", + "ReadOnly": false, + "Consistency": "", + "BindOptions": { + "Propagation": "", + "NonRecursive": false + }, + "VolumeOptions": { + "NoCopy": false, + "Labels": { + "": "" + }, + "DriverConfig": { + "Name": "", + "Options": { + "": "" + } + } + }, + "TmpfsOptions": { + "SizeBytes": 0, + "Mode": 0 + } + } + ], + "network": "", + "capadd": [ + "c1", + "c2" + ], + "capdrop": [ + "c3", + "c4" + ], + "portBindings": [ + { + "80": [ + { + "HostIp": "0.0.0.0", + "HostPort": "8080" + } + ] + } + ], + "restartPolicy": { + "Name": "", + "MaximumRetryCount": 0 + } + }}} \ No newline at end of file