Skip to content

Commit

Permalink
Support custom templating in image section during lint
Browse files Browse the repository at this point in the history
Images are validated for valid digests during linting. If the image
digest is templating and no default value exist in the bundle definition
the linting will fail, because the digest will be invalid.

One gotcha here is that if only part of the digest is templated linting
will still fail.

Signed-off-by: Kim Christensen <[email protected]>
  • Loading branch information
kichristensen committed Aug 31, 2024
1 parent 3829380 commit 87c12d9
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 19 deletions.
2 changes: 1 addition & 1 deletion pkg/cnab/config-adapter/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

func LoadTestBundle(t *testing.T, config *config.Config, path string) cnab.ExtendedBundle {
ctx := context.Background()
m, err := manifest.ReadManifest(config.Context, path, config)
m, err := manifest.ReadManifest(config.Context, path, config, false)
require.NoError(t, err)
b, err := ConvertToTestBundle(ctx, config, m)
require.NoError(t, err)
Expand Down
16 changes: 13 additions & 3 deletions pkg/manifest/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -1291,7 +1291,7 @@ func ReadManifestData(cxt *portercontext.Context, path string) ([]byte, error) {

// ReadManifest determines if specified path is a URL or a filepath.
// After reading the data in the path it returns a Manifest and any errors
func ReadManifest(cxt *portercontext.Context, path string, config *config.Config) (*Manifest, error) {
func ReadManifest(cxt *portercontext.Context, path string, config *config.Config, allowMissingVariables bool) (*Manifest, error) {
data, err := ReadManifestData(cxt, path)
if err != nil {
return nil, err
Expand All @@ -1307,7 +1307,7 @@ func ReadManifest(cxt *portercontext.Context, path string, config *config.Config
if err != nil {
return nil, fmt.Errorf("failed to marshal image section: %w", err)
}
mustache.AllowMissingVariables = false
mustache.AllowMissingVariables = allowMissingVariables
imageContext := map[string]interface{}{
"bundle": map[string]interface{}{
"custom": m.Custom,
Expand Down Expand Up @@ -1444,10 +1444,20 @@ func (m *Manifest) getTemplateVariables(data string) (map[string]struct{}, error
// LoadManifestFrom reads and validates the manifest at the specified location,
// and returns a populated Manifest structure.
func LoadManifestFrom(ctx context.Context, config *config.Config, file string) (*Manifest, error) {
return loadManifestFrom(ctx, config, file, false)
}

// LoadManifestFromAllowMissingVariables reads and validates the manifest at the specified location,
// and returns a populated Manifest structure, while allowing missing variables.
func LoadManifestFromAllowMissingVariables(ctx context.Context, config *config.Config, file string) (*Manifest, error) {
return loadManifestFrom(ctx, config, file, true)
}

func loadManifestFrom(ctx context.Context, config *config.Config, file string, allowMissingVariables bool) (*Manifest, error) {
ctx, log := tracing.StartSpan(ctx)
defer log.EndSpan()

m, err := ReadManifest(config.Context, file, config)
m, err := ReadManifest(config.Context, file, config, allowMissingVariables)
if err != nil {
return nil, err
}
Expand Down
26 changes: 13 additions & 13 deletions pkg/manifest/manifest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ func TestManifest_Validate_SchemaVersion(t *testing.T) {
cfg.TestContext.UseFilesystem()
cfg.Data.SchemaCheck = string(schema.CheckStrategyExact)

m, err := ReadManifest(cfg.Context, "testdata/porter.yaml", cfg.Config)
m, err := ReadManifest(cfg.Context, "testdata/porter.yaml", cfg.Config, false)
require.NoError(t, err)

err = m.Validate(ctx, cfg.Config)
Expand All @@ -281,7 +281,7 @@ func TestManifest_Validate_SchemaVersion(t *testing.T) {
cfg.TestContext.EditYaml("porter.yaml", func(yq *yaml.Editor) error {
return yq.SetValue("schemaVersion", "1.1.0")
})
m, err := ReadManifest(cfg.Context, "porter.yaml", cfg.Config)
m, err := ReadManifest(cfg.Context, "porter.yaml", cfg.Config, false)
require.NoError(t, err)

err = m.Validate(ctx, cfg.Config)
Expand All @@ -300,7 +300,7 @@ func TestManifest_Validate_SchemaVersion(t *testing.T) {
defer span.EndSpan()
cfg.Data.SchemaCheck = string(schema.CheckStrategyNone)

m, err := ReadManifest(cfg.Context, "testdata/porter.yaml", cfg.Config)
m, err := ReadManifest(cfg.Context, "testdata/porter.yaml", cfg.Config, false)
require.NoError(t, err)

m.SchemaVersion = ""
Expand Down Expand Up @@ -415,15 +415,15 @@ func TestManifest_Validate_WrongSchema(t *testing.T) {
func TestReadManifest_URL(t *testing.T) {
cxt := portercontext.NewTestContext(t)
url := "https://raw.githubusercontent.com/getporter/porter/v0.27.1/pkg/manifest/testdata/simple.porter.yaml"
m, err := ReadManifest(cxt.Context, url, config.NewTestConfig(t).Config)
m, err := ReadManifest(cxt.Context, url, config.NewTestConfig(t).Config, false)

require.NoError(t, err)
assert.Equal(t, "hello", m.Name)
}

func TestReadManifest_Validate_InvalidURL(t *testing.T) {
cxt := portercontext.NewTestContext(t)
_, err := ReadManifest(cxt.Context, "http://fake-example-porter", config.NewTestConfig(t).Config)
_, err := ReadManifest(cxt.Context, "http://fake-example-porter", config.NewTestConfig(t).Config, false)

assert.Error(t, err)
assert.Regexp(t, "could not reach url http://fake-example-porter", err)
Expand All @@ -432,7 +432,7 @@ func TestReadManifest_Validate_InvalidURL(t *testing.T) {
func TestReadManifest_File(t *testing.T) {
cxt := portercontext.NewTestContext(t)
cxt.AddTestFile("testdata/simple.porter.yaml", config.Name)
m, err := ReadManifest(cxt.Context, config.Name, config.NewTestConfig(t).Config)
m, err := ReadManifest(cxt.Context, config.Name, config.NewTestConfig(t).Config, false)

require.NoError(t, err)
assert.Equal(t, "hello", m.Name)
Expand Down Expand Up @@ -562,15 +562,15 @@ func TestSetDefaults(t *testing.T) {

func TestReadManifest_Validate_MissingFile(t *testing.T) {
cxt := portercontext.NewTestContext(t)
_, err := ReadManifest(cxt.Context, "fake-porter.yaml", config.NewTestConfig(t).Config)
_, err := ReadManifest(cxt.Context, "fake-porter.yaml", config.NewTestConfig(t).Config, false)

assert.EqualError(t, err, "the specified porter configuration file fake-porter.yaml does not exist")
}

func TestMixinDeclaration_UnmarshalYAML(t *testing.T) {
cxt := portercontext.NewTestContext(t)
cxt.AddTestFile("testdata/mixin-with-config.yaml", config.Name)
m, err := ReadManifest(cxt.Context, config.Name, config.NewTestConfig(t).Config)
m, err := ReadManifest(cxt.Context, config.Name, config.NewTestConfig(t).Config, false)

require.NoError(t, err)
assert.Len(t, m.Mixins, 3, "expected 3 mixins")
Expand All @@ -583,7 +583,7 @@ func TestMixinDeclaration_UnmarshalYAML(t *testing.T) {
func TestMixinDeclaration_UnmarshalYAML_Invalid(t *testing.T) {
cxt := portercontext.NewTestContext(t)
cxt.AddTestFile("testdata/mixin-with-bad-config.yaml", config.Name)
_, err := ReadManifest(cxt.Context, config.Name, config.NewTestConfig(t).Config)
_, err := ReadManifest(cxt.Context, config.Name, config.NewTestConfig(t).Config, false)

require.Error(t, err)
assert.Contains(t, err.Error(), "mixin declaration contained more than one mixin")
Expand All @@ -598,7 +598,7 @@ func TestCredentialsDefinition_UnmarshalYAML(t *testing.T) {
t.Run("all credentials in the generated manifest file are required", func(t *testing.T) {
cxt := portercontext.NewTestContext(t)
cxt.AddTestFile("testdata/with-credentials.yaml", config.Name)
m, err := ReadManifest(cxt.Context, config.Name, config.NewTestConfig(t).Config)
m, err := ReadManifest(cxt.Context, config.Name, config.NewTestConfig(t).Config, false)
require.NoError(t, err)
assertAllCredentialsRequired(t, m.Credentials)

Expand Down Expand Up @@ -896,7 +896,7 @@ func TestLoadManifestWithRequiredExtensions(t *testing.T) {
func TestReadManifest_WithTemplateVariables(t *testing.T) {
cxt := portercontext.NewTestContext(t)
cxt.AddTestFile("testdata/porter-with-templating.yaml", config.Name)
m, err := ReadManifest(cxt.Context, config.Name, config.NewTestConfig(t).Config)
m, err := ReadManifest(cxt.Context, config.Name, config.NewTestConfig(t).Config, false)
require.NoError(t, err, "ReadManifest failed")
wantVars := []string{"bundle.dependencies.mysql.outputs.mysql-password", "bundle.outputs.msg", "bundle.outputs.name"}
assert.Equal(t, wantVars, m.TemplateVariables)
Expand All @@ -905,7 +905,7 @@ func TestReadManifest_WithTemplateVariables(t *testing.T) {
func TestManifest_GetTemplatedOutputs(t *testing.T) {
cxt := portercontext.NewTestContext(t)
cxt.AddTestFile("testdata/porter-with-templating.yaml", config.Name)
m, err := ReadManifest(cxt.Context, config.Name, config.NewTestConfig(t).Config)
m, err := ReadManifest(cxt.Context, config.Name, config.NewTestConfig(t).Config, false)
require.NoError(t, err, "ReadManifest failed")

outputs := m.GetTemplatedOutputs()
Expand All @@ -917,7 +917,7 @@ func TestManifest_GetTemplatedOutputs(t *testing.T) {
func TestManifest_GetTemplatedDependencyOutputs(t *testing.T) {
cxt := portercontext.NewTestContext(t)
cxt.AddTestFile("testdata/porter-with-templating.yaml", config.Name)
m, err := ReadManifest(cxt.Context, config.Name, config.NewTestConfig(t).Config)
m, err := ReadManifest(cxt.Context, config.Name, config.NewTestConfig(t).Config, false)
require.NoError(t, err, "ReadManifest failed")

outputs := m.GetTemplatedDependencyOutputs()
Expand Down
2 changes: 1 addition & 1 deletion pkg/porter/lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func (o *LintOptions) validateFile(cxt *portercontext.Context) error {
// Lint porter.yaml for any problems and report the results.
// This calls the mixins to analyze their sections of the manifest.
func (p *Porter) Lint(ctx context.Context, opts LintOptions) (linter.Results, error) {
manifest, err := manifest.LoadManifestFrom(ctx, p.Config, opts.File)
manifest, err := manifest.LoadManifestFromAllowMissingVariables(ctx, p.Config, opts.File)
if err != nil {
return nil, err
}
Expand Down
44 changes: 44 additions & 0 deletions pkg/porter/lint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,50 @@ func TestPorter_Lint(t *testing.T) {
assert.Len(t, results, 1, "Lint returned the wrong number of results")
}

func TestPorter_LintImageCustomDataWithDefault(t *testing.T) {
p := NewTestPorter(t)
defer p.Close()

p.TestConfig.TestContext.AddTestFile("testdata/porter-image-custom-data-default.yaml", "porter.yaml")

mixins := p.Mixins.(*mixin.TestMixinProvider)
mixins.LintResults = linter.Results{
{
Level: linter.LevelError,
},
}

var opts LintOptions
err := opts.Validate(p.Context)
require.NoError(t, err, "Validate failed")

results, err := p.Lint(context.Background(), opts)
require.NoError(t, err, "Lint failed")
assert.Len(t, results, 1, "Lint returned the wrong number of results")
}

func TestPorter_LintImageCustomDataWithoutDefault(t *testing.T) {
p := NewTestPorter(t)
defer p.Close()

p.TestConfig.TestContext.AddTestFile("testdata/porter-image-custom-data-no-default.yaml", "porter.yaml")

mixins := p.Mixins.(*mixin.TestMixinProvider)
mixins.LintResults = linter.Results{
{
Level: linter.LevelError,
},
}

var opts LintOptions
err := opts.Validate(p.Context)
require.NoError(t, err, "Validate failed")

results, err := p.Lint(context.Background(), opts)
require.NoError(t, err, "Lint failed")
assert.Len(t, results, 1, "Lint returned the wrong number of results")
}

func TestPorter_PrintLintResults(t *testing.T) {
lintResults := linter.Results{
{
Expand Down
39 changes: 39 additions & 0 deletions pkg/porter/testdata/porter-image-custom-data-default.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
schemaVersion: 1.0.0
name: porter-hello
version: 0.1.0
description: "A bundle with a custom action"
registry: "localhost:5000"

custom:
digest: "sha256:6b5a28ccbb76f12ce771a23757880c6083234255c5ba191fca1c5db1f71c1687"

images:
something:
description: "an image"
imageType: "docker"
repository: "getporter/boo"
digest: "${ bundle.custom.digest }"

mixins:
- exec

install:
- exec:
description: "Install Hello World"
command: echo
arguments:
- "Hello world"

upgrade:
- exec:
description: "World 2.0"
command: bash
flags:
c: echo World 2.0

uninstall:
- exec:
description: "Uninstall Hello World"
command: bash
flags:
c: echo Goodbye World
36 changes: 36 additions & 0 deletions pkg/porter/testdata/porter-image-custom-data-no-default.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
schemaVersion: 1.0.0
name: porter-hello
version: 0.1.0
description: "A bundle with a custom action"
registry: "localhost:5000"

images:
something:
description: "an image"
imageType: "docker"
repository: "getporter/boo"
digest: "${ bundle.custom.digest }"

mixins:
- exec

install:
- exec:
description: "Install Hello World"
command: echo
arguments:
- "Hello world"

upgrade:
- exec:
description: "World 2.0"
command: bash
flags:
c: echo World 2.0

uninstall:
- exec:
description: "Uninstall Hello World"
command: bash
flags:
c: echo Goodbye World
2 changes: 1 addition & 1 deletion pkg/runtime/runtime_manifest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
func runtimeManifestFromStepYaml(t *testing.T, testConfig *config.TestConfig, stepYaml string) *RuntimeManifest {
mContent := []byte(stepYaml)
require.NoError(t, testConfig.FileSystem.WriteFile("/cnab/app/porter.yaml", mContent, pkg.FileModeWritable))
m, err := manifest.ReadManifest(testConfig.Context, "/cnab/app/porter.yaml", testConfig.Config)
m, err := manifest.ReadManifest(testConfig.Context, "/cnab/app/porter.yaml", testConfig.Config, false)
require.NoError(t, err, "ReadManifest failed")
cfg := NewConfigFor(testConfig.Config)
return NewRuntimeManifest(cfg, cnab.ActionInstall, m)
Expand Down

0 comments on commit 87c12d9

Please sign in to comment.