Skip to content

Commit

Permalink
Convert a depsv2 porter.yaml into bundle.json
Browse files Browse the repository at this point in the history
This adds support for the dependencies-v2 experimental flag when building a bundle.

* Add dependency v2 fields to the porter.yaml manifest
* Represent v2 deps in the bundle.json
* Determine which dependency extension is used by a bundle
* Have separate packages for the dependencies v2 cnab extension, and its implementation
* Consolidate extensions definition into a single file
* Match file names to contained structs in pkg/cnab

I have updated the mybuns test bundle with additional data so that it can be used in some of our unit tests (such as the cnab-adapter tests) so that we aren't maintaining a bunch of random test porter.yaml files that are all slightly different to hit all our required test cases. I'll follow up later and see if we can reuse it even more elsewhere.

While not an offially released version of the manifest, I have reserved 1.0.2 for bundles that define advanced dependencies. The schemaVersion of a bundle can only be set to 1.0.2 when the v2 dependencies experimental feature is enabled.

The docs still point to 1.0.1 as the latest version, and by default bundles are created with 1.0.1 and validated against 1.0.1. Once we are sure that our schema changes are solid and won't be modified further under the experimental flag (ideally waiting until the flag is removed) then we can release 1.0.2 and default to it.

Signed-off-by: Carolyn Van Slyck <me@carolynvanslyck.com>
  • Loading branch information
carolynvs committed Mar 28, 2023
1 parent fd0d90e commit d6d0bc4
Show file tree
Hide file tree
Showing 33 changed files with 3,022 additions and 347 deletions.
239 changes: 239 additions & 0 deletions docs/content/bundle/manifest/file-format/1.0.2.md

Large diffs are not rendered by default.

50 changes: 49 additions & 1 deletion pkg/cnab/config-adapter/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"get.porter.sh/porter/pkg/cnab"
depsv1ext "get.porter.sh/porter/pkg/cnab/extensions/dependencies/v1"
depsv2ext "get.porter.sh/porter/pkg/cnab/extensions/dependencies/v2"
"get.porter.sh/porter/pkg/config"
"get.porter.sh/porter/pkg/experimental"
"get.porter.sh/porter/pkg/manifest"
Expand Down Expand Up @@ -414,9 +415,12 @@ func (c *ManifestConverter) generateDependencies() (interface{}, string, error)

// Check if they are using v1 of the dependencies spec or v2
if c.config.IsFeatureEnabled(experimental.FlagDependenciesV2) {
panic("the dependencies-v2 experimental flag was specified but is not yet implemented")
// Ok we are using v2!
deps, err := c.generateDependenciesV2()
return deps, cnab.DependenciesV2ExtensionKey, err
}

// Default to using v1 of deps
deps := c.generateDependenciesV1()
return deps, cnab.DependenciesV1ExtensionKey, nil
}
Expand Down Expand Up @@ -454,6 +458,48 @@ func (c *ManifestConverter) generateDependenciesV1() *depsv1ext.Dependencies {
return deps
}

func (c *ManifestConverter) generateDependenciesV2() (*depsv2ext.Dependencies, error) {
deps := &depsv2ext.Dependencies{
Requires: make(map[string]depsv2ext.Dependency, len(c.Manifest.Dependencies.Requires)),
}

for _, dep := range c.Manifest.Dependencies.Requires {
dependencyRef := depsv2ext.Dependency{
Name: dep.Name,
Bundle: dep.Bundle.Reference,
Version: dep.Bundle.Version,
Sharing: depsv2ext.SharingCriteria{
Mode: dep.Sharing.GetEffectiveMode(),
Group: depsv2ext.SharingGroup{
Name: dep.Sharing.Group.Name,
},
},
Parameters: dep.Parameters,
Credentials: dep.Credentials,
Outputs: dep.Outputs,
}

if dep.Bundle.Interface != nil {
dependencyRef.Interface = &depsv2ext.DependencyInterface{
ID: dep.Bundle.Interface.ID,
Reference: dep.Bundle.Interface.Reference,
}

// Porter doesn't let you embed a random bundle.json document into your porter.yaml
// While the CNAB spec lets the document be anything, we constrain the interface to a porter representation of the bundle's parameters, credentials and outputs.
if dep.Bundle.Interface.Document != nil {
// TODO(PEP003): Convert the parameters, credentials and outputs defined on manifest.BundleInterfaceDocument and create an (incomplete) bundle.json from it
// See https://github.com/getporter/porter/issues/2548
panic("conversion of an embedded bundle interface document for a dependency is not implemented")
}
}

deps.Requires[dep.Name] = dependencyRef
}

return deps, nil
}

func (c *ManifestConverter) generateParameterSources(b *cnab.ExtendedBundle) cnab.ParameterSources {
ps := cnab.ParameterSources{}

Expand Down Expand Up @@ -640,6 +686,8 @@ func (c *ManifestConverter) generateRequiredExtensions(b cnab.ExtendedBundle) []
// Add the appropriate dependencies key if applicable
if b.HasDependenciesV1() {
requiredExtensions = append(requiredExtensions, cnab.DependenciesV1ExtensionKey)
} else if b.HasDependenciesV2() {
requiredExtensions = append(requiredExtensions, cnab.DependenciesV2ExtensionKey)
}

// Add the appropriate parameter sources key if applicable
Expand Down
135 changes: 107 additions & 28 deletions pkg/cnab/config-adapter/adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import (

"get.porter.sh/porter/pkg/cnab"
depsv1ext "get.porter.sh/porter/pkg/cnab/extensions/dependencies/v1"
depsv2ext "get.porter.sh/porter/pkg/cnab/extensions/dependencies/v2"
"get.porter.sh/porter/pkg/config"
"get.porter.sh/porter/pkg/experimental"
"get.porter.sh/porter/pkg/manifest"
"get.porter.sh/porter/pkg/mixin"
"get.porter.sh/porter/pkg/pkgmgmt"
Expand All @@ -22,27 +24,53 @@ import (
func TestManifestConverter(t *testing.T) {
t.Parallel()

c := config.NewTestConfig(t)
c.TestContext.AddTestFileFromRoot("tests/testdata/mybuns/porter.yaml", config.Name)
testcases := []struct {
name string
configHandler func(c *config.Config)
manifestPath string
goldenFile string
}{
{name: "depsv1",
configHandler: func(c *config.Config) {},
manifestPath: "tests/testdata/mybuns/porter.yaml",
goldenFile: "testdata/mybuns-depsv1.bundle.json"},
{name: "depsv2",
configHandler: func(c *config.Config) {
c.SetExperimentalFlags(experimental.FlagDependenciesV2)
},
manifestPath: "tests/testdata/mybuns/porter.yaml",
goldenFile: "testdata/mybuns-depsv2.bundle.json"},
}

ctx := context.Background()
m, err := manifest.LoadManifestFrom(ctx, c.Config, config.Name)
require.NoError(t, err, "could not load manifest")
for _, tc := range testcases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

installedMixins := []mixin.Metadata{
{Name: "exec", VersionInfo: pkgmgmt.VersionInfo{Version: "v1.2.3"}},
}
c := config.NewTestConfig(t)
tc.configHandler(c.Config)
c.TestContext.AddTestFileFromRoot(tc.manifestPath, config.Name)

a := NewManifestConverter(c.Config, m, nil, installedMixins)
ctx := context.Background()
m, err := manifest.LoadManifestFrom(ctx, c.Config, config.Name)
require.NoError(t, err, "could not load manifest")

bun, err := a.ToBundle(ctx)
require.NoError(t, err, "ToBundle failed")
installedMixins := []mixin.Metadata{
{Name: "exec", VersionInfo: pkgmgmt.VersionInfo{Version: "v1.2.3"}},
}

a := NewManifestConverter(c.Config, m, nil, installedMixins)

// Compare the regular json, not the canonical, because that's hard to diff
prepBundleForDiff(&bun.Bundle)
bunD, err := json.MarshalIndent(bun, "", " ")
require.NoError(t, err)
c.TestContext.CompareGoldenFile("testdata/mybuns.bundle.json", string(bunD))
bun, err := a.ToBundle(ctx)
require.NoError(t, err, "ToBundle failed")

// Compare the regular json, not the canonical, because that's hard to diff
prepBundleForDiff(&bun.Bundle)
bunD, err := json.MarshalIndent(bun, "", " ")
require.NoError(t, err)
c.TestContext.CompareGoldenFile(tc.goldenFile, string(bunD))
})
}
}

func prepBundleForDiff(b *bundle.Bundle) {
Expand Down Expand Up @@ -571,7 +599,7 @@ func TestManifestConverter_generateDependenciesv1(t *testing.T) {
t.Parallel()

c := config.NewTestConfig(t)
c.TestContext.AddTestFile("testdata/porter-with-deps.yaml", config.Name)
c.TestContext.AddTestFile("testdata/porter.yaml", config.Name)

ctx := context.Background()
m, err := manifest.LoadManifestFrom(ctx, c.Config, config.Name)
Expand Down Expand Up @@ -601,21 +629,72 @@ func TestManifestConverter_generateDependenciesv1(t *testing.T) {
}
}

func TestManifestConverter_generateRequiredExtensions_Dependencies(t *testing.T) {
func TestManifestConverter_generateDependenciesv2(t *testing.T) {
t.Parallel()

c := config.NewTestConfig(t)
c.TestContext.AddTestFile("testdata/porter-with-deps.yaml", config.Name)
testcases := []struct {
name string
wantDep depsv2ext.Dependency
}{
{"all fields", depsv2ext.Dependency{
Name: "mysql",
Bundle: "getporter/azure-mysql:5.7",
Version: "5.7.x",
Interface: &depsv2ext.DependencyInterface{
ID: "https://getporter.org/interfaces/#mysql",
Reference: "getporter/mysql-spec:5.7",
// TODO(PEP003): Implement with https://github.com/getporter/porter/issues/2548
//Document: nil,
},
Sharing: depsv2ext.SharingCriteria{
Mode: depsv2ext.SharingModeGroup,
Group: depsv2ext.SharingGroup{Name: "myapp"},
},
Parameters: map[string]string{
"database": "wordpress",
"collation": "${bundle.parameters.db_collation}",
},
Credentials: map[string]string{
"user": "${bundle.credentials.username}",
},
}},
}

ctx := context.Background()
m, err := manifest.LoadManifestFrom(ctx, c.Config, config.Name)
require.NoError(t, err, "could not load manifest")
for _, tc := range testcases {
tc := tc

a := NewManifestConverter(c.Config, m, nil, nil)
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

bun, err := a.ToBundle(ctx)
require.NoError(t, err, "ToBundle failed")
assert.Contains(t, bun.RequiredExtensions, "io.cnab.dependencies")
c := config.NewTestConfig(t)
c.SetExperimentalFlags(experimental.FlagDependenciesV2)
c.TestContext.AddTestFile("testdata/porter-with-depsv2.yaml", config.Name)

ctx := context.Background()
m, err := manifest.LoadManifestFrom(ctx, c.Config, config.Name)
require.NoError(t, err, "could not load manifest")

a := NewManifestConverter(c.Config, m, nil, nil)

depsExt, depsExtKey, err := a.generateDependencies()
require.NoError(t, err)
require.Equal(t, cnab.DependenciesV2ExtensionKey, depsExtKey, "expected the v2 dependencies extension key")
require.IsType(t, &depsv2ext.Dependencies{}, depsExt, "expected a v2 dependencies extension section")
deps := depsExt.(*depsv2ext.Dependencies)
require.Len(t, deps.Requires, 3, "incorrect number of dependencies were generated")

var dep *depsv2ext.Dependency
for _, d := range deps.Requires {
if d.Bundle == tc.wantDep.Bundle {
dep = &d
break
}
}

require.NotNil(t, dep, "could not find bundle %s", tc.wantDep.Bundle)
assert.Equal(t, &tc.wantDep, dep)
})
}
}

func TestManifestConverter_generateParameterSources(t *testing.T) {
Expand Down Expand Up @@ -880,7 +959,7 @@ func TestManifestConverter_generateCustomMetadata(t *testing.T) {
_, err = bun.WriteTo(f)
require.NoError(t, err, "Failed to write bundle file")

expectedCustomMetaData := "{\"foo\":{\"test1\":true,\"test2\":1,\"test3\":\"value\",\"test4\":[\"one\",\"two\",\"three\"],\"test5\":{\"1\":\"one\",\"two\":\"two\"}}"
expectedCustomMetaData := `"foo":{"test1":true,"test2":1,"test3":"value","test4":["one","two","three"],"test5":{"1":"one","two":"two"}}`
bundleData, err := os.ReadFile(f.Name())
require.NoError(t, err, "Failed to read bundle file")

Expand Down
12 changes: 12 additions & 0 deletions pkg/cnab/config-adapter/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,24 @@ package configadapter

import (
"context"
"testing"

"github.com/stretchr/testify/require"

"get.porter.sh/porter/pkg/cnab"
"get.porter.sh/porter/pkg/config"
"get.porter.sh/porter/pkg/manifest"
)

func LoadTestBundle(t *testing.T, config *config.Config, path string) cnab.ExtendedBundle {
ctx := context.Background()
m, err := manifest.ReadManifest(config.Context, path)
require.NoError(t, err)
b, err := ConvertToTestBundle(ctx, config, m)
require.NoError(t, err)
return b
}

// ConvertToTestBundle is suitable for taking a test manifest (porter.yaml)
// and making a bundle.json for it. Does not make an accurate representation
// of the bundle, but is suitable for testing.
Expand Down
Loading

0 comments on commit d6d0bc4

Please sign in to comment.