Skip to content

Commit 21bb79f

Browse files
authored
feat(*): enable parameters of type file in Porter manifest (#579)
1 parent d26d6f5 commit 21bb79f

22 files changed

+369
-14
lines changed

Gopkg.lock

+4-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Gopkg.toml

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
[[constraint]]
22
name = "github.com/deislabs/cnab-go"
3-
version = "v0.4.0-beta1"
3+
source = "github.com/vdice/cnab-go"
4+
branch = "fix/custom-validators"
45

56
[[override]]
67
name = "github.com/deislabs/cnab-go"
7-
version = "v0.4.0-beta1"
8+
source = "github.com/vdice/cnab-go"
9+
branch = "fix/custom-validators"
810

911
# Using master until there is a release of cnab-to-oci
1012
[[constraint]]

pkg/cnab/config_adapter/adapter.go

+4
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,10 @@ func (c *ManifestConverter) generateBundleParameters(defs *definition.Definition
163163
// (Both Params and Outputs may reference same Definition)
164164
if _, exists := (*defs)[param.Name]; !exists {
165165
def := param.Schema
166+
if def.Type == "file" {
167+
def.Type = "string"
168+
def.ContentEncoding = "base64"
169+
}
166170
(*defs)[param.Name] = &def
167171
}
168172
params[param.Name] = p

pkg/cnab/config_adapter/adapter_test.go

+14
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,20 @@ func TestManifestConverter_generateBundleParametersSchema(t *testing.T) {
172172
Default: `"myobject": { "foo": "true", "bar": [ 1, 2, 3 ] }`,
173173
},
174174
},
175+
{
176+
"afile",
177+
bundle.Parameter{
178+
Definition: "afile",
179+
Destination: &bundle.Location{
180+
Path: "/root/.kube/config",
181+
},
182+
Required: true,
183+
},
184+
definition.Schema{
185+
Type: "string",
186+
ContentEncoding: "base64",
187+
},
188+
},
175189
}
176190

177191
for _, tc := range testcases {

pkg/cnab/config_adapter/testdata/porter-with-parameters.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ parameters:
4747
3
4848
]
4949
}'
50+
- name: afile
51+
type: file
52+
path: /root/.kube/config
5053

5154
mixins:
5255
- exec

pkg/cnab/provider/parameters.go

+22-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package cnabprovider
22

33
import (
4+
"encoding/base64"
45
"fmt"
56

67
"github.com/deislabs/cnab-go/bundle"
8+
"github.com/deislabs/cnab-go/bundle/definition"
79
"github.com/deislabs/cnab-go/claim"
810
"github.com/pkg/errors"
911
)
@@ -25,7 +27,12 @@ func (d *Duffle) loadParameters(claim *claim.Claim, rawOverrides map[string]stri
2527
return nil, fmt.Errorf("definition %s not defined in bundle", param.Definition)
2628
}
2729

28-
value, err := def.ConvertValue(rawValue)
30+
unconverted, err := d.getUnconvertedValueFromRaw(def, key, rawValue)
31+
if err != nil {
32+
return nil, err
33+
}
34+
35+
value, err := def.ConvertValue(unconverted)
2936
if err != nil {
3037
return nil, errors.Wrapf(err, "unable to convert parameter's %s value %s to the destination parameter type %s", key, rawValue, def.Type)
3138
}
@@ -63,6 +70,20 @@ func (d *Duffle) loadParameters(claim *claim.Claim, rawOverrides map[string]stri
6370
return bundle.ValuesOrDefaults(overrides, bun)
6471
}
6572

73+
func (d *Duffle) getUnconvertedValueFromRaw(def *definition.Schema, key, rawValue string) (string, error) {
74+
// the parameter value (via rawValue) may represent a file on the local filesystem
75+
if def.Type == "string" && def.ContentEncoding == "base64" {
76+
if _, err := d.FileSystem.Stat(rawValue); err == nil {
77+
bytes, err := d.FileSystem.ReadFile(rawValue)
78+
if err != nil {
79+
return "", errors.Wrapf(err, "unable to read file parameter %s", key)
80+
}
81+
return base64.StdEncoding.EncodeToString(bytes), nil
82+
}
83+
}
84+
return rawValue, nil
85+
}
86+
6687
// TODO: remove in favor of cnab-go logic: https://github.com/deislabs/cnab-go/pull/99
6788
func appliesToAction(action string, parameter bundle.Parameter) bool {
6889
if len(parameter.ApplyTo) == 0 {

pkg/cnab/provider/parameters_test.go

+37
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,40 @@ func Test_loadParameters_requiredButDoesNotApply(t *testing.T) {
189189

190190
require.Equal(t, "foo-claim-value", params["foo"], "expected param 'foo' to be the bundle default")
191191
}
192+
193+
func Test_loadParameters_fileParameter(t *testing.T) {
194+
c := config.NewTestConfig(t)
195+
d := NewDuffle(c.Config)
196+
197+
c.TestContext.AddTestFile("testdata/file-param", "/path/to/file")
198+
199+
claim, err := claim.New("test")
200+
require.NoError(t, err)
201+
202+
claim.Bundle = &bundle.Bundle{
203+
Definitions: definition.Definitions{
204+
"foo": &definition.Schema{
205+
Type: "string",
206+
ContentEncoding: "base64",
207+
},
208+
},
209+
Parameters: map[string]bundle.Parameter{
210+
"foo": bundle.Parameter{
211+
Definition: "foo",
212+
Required: true,
213+
Destination: &bundle.Location{
214+
Path: "/tmp/foo",
215+
},
216+
},
217+
},
218+
}
219+
220+
overrides := map[string]string{
221+
"foo": "/path/to/file",
222+
}
223+
224+
params, err := d.loadParameters(claim, overrides, "action")
225+
require.NoError(t, err)
226+
227+
require.Equal(t, "SGVsbG8gV29ybGQh", params["foo"], "expected param 'foo' to be the base64-encoded file contents")
228+
}

pkg/cnab/provider/testdata/file-param

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Hello World!

pkg/config/manifest.go

+54-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package config
22

33
import (
4+
"fmt"
45
"io/ioutil"
56
"net/http"
67
"reflect"
@@ -88,6 +89,13 @@ func (m *Manifest) Validate() error {
8889
}
8990
}
9091

92+
for _, parameter := range m.Parameters {
93+
err = parameter.Validate()
94+
if err != nil {
95+
result = multierror.Append(result, err)
96+
}
97+
}
98+
9199
return result
92100
}
93101

@@ -104,6 +112,45 @@ type ParameterDefinition struct {
104112
definition.Schema `yaml:",inline"`
105113
}
106114

115+
func (pd *ParameterDefinition) Validate() error {
116+
var result *multierror.Error
117+
118+
if pd.Name == "" {
119+
result = multierror.Append(result, errors.New("parameter name is required"))
120+
}
121+
122+
// Porter supports declaring a parameter of type: "file",
123+
// which we will convert to the appropriate bundle.Parameter type in adapter.go
124+
// Here, we copy the ParameterDefinition and make the same modification before validation
125+
pdCopy := pd.DeepCopy()
126+
if pdCopy.Type == "file" {
127+
if pd.Destination.Path == "" {
128+
result = multierror.Append(result, fmt.Errorf("no destination path supplied for parameter %s", pd.Name))
129+
}
130+
pdCopy.Type = "string"
131+
pdCopy.ContentEncoding = "base64"
132+
}
133+
134+
schemaValidationErrs, err := pdCopy.Schema.Validate(pdCopy)
135+
if err != nil {
136+
result = multierror.Append(result, errors.Wrapf(err, "encountered error while validating parameter %s", pdCopy.Name))
137+
}
138+
for _, schemaValidationErr := range schemaValidationErrs {
139+
result = multierror.Append(result, errors.Wrapf(err, "encountered validation error(s) for parameter %s: %v", pdCopy.Name, schemaValidationErr))
140+
}
141+
142+
return result.ErrorOrNil()
143+
}
144+
145+
// DeepCopy copies a ParameterDefinition and returns the copy
146+
func (pd *ParameterDefinition) DeepCopy() *ParameterDefinition {
147+
var p2 ParameterDefinition
148+
p2 = *pd
149+
p2.ApplyTo = make([]string, len(pd.ApplyTo))
150+
copy(p2.ApplyTo, pd.ApplyTo)
151+
return &p2
152+
}
153+
107154
type CredentialDefinition struct {
108155
Name string `yaml:"name"`
109156
Description string `yaml:"description,omitempty"`
@@ -239,7 +286,13 @@ func (od *OutputDefinition) Validate() error {
239286
return errors.New("output name is required")
240287
}
241288

242-
// TODO: Validate inline Schema
289+
schemaValidationErrs, err := od.Schema.Validate(od)
290+
if err != nil {
291+
return errors.Wrapf(err, "encountered error while validating output %s", od.Name)
292+
}
293+
if len(schemaValidationErrs) != 0 {
294+
return errors.Wrapf(err, "encountered validation error(s) for output %s: %v", od.Name, schemaValidationErrs)
295+
}
243296

244297
return nil
245298
}

pkg/config/manifest_test.go

+23
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"io/ioutil"
55
"testing"
66

7+
"github.com/deislabs/cnab-go/bundle/definition"
78
"gopkg.in/yaml.v2"
89

910
"github.com/stretchr/testify/assert"
@@ -82,3 +83,25 @@ func TestMixinDeclaration_MarshalYAML(t *testing.T) {
8283

8384
assert.Equal(t, string(wantYaml), string(gotYaml))
8485
}
86+
87+
func TestValidateParameterDefinition(t *testing.T) {
88+
pd := ParameterDefinition{
89+
Name: "myparam",
90+
Schema: definition.Schema{
91+
Type: "file",
92+
},
93+
}
94+
95+
pd.Destination = Location{}
96+
97+
err := pd.Validate()
98+
assert.EqualError(t, err, `1 error occurred:
99+
* no destination path supplied for parameter myparam
100+
101+
`)
102+
103+
pd.Destination.Path = "/path/to/file"
104+
105+
err = pd.Validate()
106+
assert.NoError(t, err)
107+
}

pkg/config/runtime-manifest.go

+31-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package config
22

33
import (
4+
"encoding/base64"
45
"fmt"
56
"os"
67
"reflect"
@@ -85,7 +86,6 @@ func resolveParameter(pd ParameterDefinition) (string, error) {
8586
return pd.Destination.Path, nil
8687
}
8788
return "", fmt.Errorf("parameter: %s is malformed", pd.Name)
88-
8989
}
9090

9191
func resolveCredential(cd CredentialDefinition) (string, error) {
@@ -275,3 +275,33 @@ func (m *RuntimeManifest) ResolveStep(step *Step) error {
275275

276276
return nil
277277
}
278+
279+
// Prepare prepares the runtime environment prior to step execution
280+
func (m *RuntimeManifest) Prepare() error {
281+
// For parameters of type "file", we may need to decode files on the filesystem
282+
// before execution of the step/action
283+
for _, param := range m.Parameters {
284+
if param.Type == "file" {
285+
if param.Destination.Path == "" {
286+
return fmt.Errorf("destination path is not supplied for parameter %s", param.Name)
287+
}
288+
289+
// Porter by default places parameter value into file determined by Destination.Path
290+
bytes, err := m.FileSystem.ReadFile(param.Destination.Path)
291+
if err != nil {
292+
return fmt.Errorf("unable to acquire value for parameter %s", param.Name)
293+
}
294+
295+
decoded, err := base64.StdEncoding.DecodeString(string(bytes))
296+
if err != nil {
297+
return errors.Wrapf(err, "unable to decode parameter %s", param.Name)
298+
}
299+
300+
err = m.FileSystem.WriteFile(param.Destination.Path, decoded, os.ModePerm)
301+
if err != nil {
302+
return errors.Wrapf(err, "unable to write decoded parameter %s", param.Name)
303+
}
304+
}
305+
}
306+
return nil
307+
}

0 commit comments

Comments
 (0)