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

Signed-off-by: Carolyn Van Slyck <me@carolynvanslyck.com>
  • Loading branch information
carolynvs committed Oct 25, 2022
1 parent 24461db commit 242ca7e
Show file tree
Hide file tree
Showing 43 changed files with 1,490 additions and 263 deletions.
4 changes: 2 additions & 2 deletions magefile.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,8 @@ func TestUnit() {

must.Command("go", "test", v, "./...").CollapseArgs().RunV()

// Verify integration tests compile since we don't run them automatically on pull requests
must.Run("go", "test", "-run=non", "-tags=integration", "./...")
// Verify integration/smoke tests compile since we don't run them automatically on pull requests
must.Run("go", "test", "-run=non", `-tags="integration smoke"`, "./...")
}

// Run smoke tests to quickly check if Porter is broken
Expand Down
File renamed without changes.
File renamed without changes.
99 changes: 87 additions & 12 deletions pkg/cnab/config-adapter/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package configadapter

import (
"context"
"encoding/json"
"fmt"
"path"
"strings"

"get.porter.sh/porter/pkg/cnab"
depsv1 "get.porter.sh/porter/pkg/cnab/dependencies/v1"
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,33 +416,33 @@ 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
}

deps, err := c.generateDependenciesV1()
if err != nil {
return nil, "", err
}
// Default to using v1 of deps
deps := c.generateDependenciesV1()
return deps, cnab.DependenciesV1ExtensionKey, nil
}

func (c *ManifestConverter) generateDependenciesV1() (*depsv1.Dependencies, error) {
func (c *ManifestConverter) generateDependenciesV1() *depsv1ext.Dependencies {
if len(c.Manifest.Dependencies.Requires) == 0 {
return nil, nil
return nil
}

deps := &depsv1.Dependencies{
deps := &depsv1ext.Dependencies{
Sequence: make([]string, 0, len(c.Manifest.Dependencies.Requires)),
Requires: make(map[string]depsv1.Dependency, len(c.Manifest.Dependencies.Requires)),
Requires: make(map[string]depsv1ext.Dependency, len(c.Manifest.Dependencies.Requires)),
}

for _, dep := range c.Manifest.Dependencies.Requires {
dependencyRef := depsv1.Dependency{
dependencyRef := depsv1ext.Dependency{
Name: dep.Name,
Bundle: dep.Bundle.Reference,
}
if len(dep.Bundle.Version) > 0 {
dependencyRef.Version = &depsv1.DependencyVersion{
dependencyRef.Version = &depsv1ext.DependencyVersion{
Ranges: []string{dep.Bundle.Version},
}

Expand All @@ -454,6 +456,77 @@ func (c *ManifestConverter) generateDependenciesV1() (*depsv1.Dependencies, erro
deps.Requires[dep.Name] = dependencyRef
}

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,
}

if dep.Bundle.Interface != nil {
if dep.Bundle.Interface.Reference != "" {
dependencyRef.Interface.Reference = dep.Bundle.Interface.Reference
}
if dep.Bundle.Interface.Document != nil {
bundleData, err := json.Marshal(dep.Bundle.Interface.Document)
if err != nil {
return nil, fmt.Errorf("invalid bundle interface document for dependency %s: %w", dep.Name, err)
}
rawMessage := &json.RawMessage{}
err = rawMessage.UnmarshalJSON(bundleData)
if err != nil {
return nil, fmt.Errorf("could not convert bundle interface document to a raw json message for dependency %s: %w", dep.Name, err)
}
dependencyRef.Interface.Document = rawMessage
}
}

if dep.Installation != nil {
dependencyRef.Installation = &depsv2ext.DependencyInstallation{
Labels: dep.Installation.Labels,
}
if dep.Installation.Criteria != nil {
dependencyRef.Installation.Criteria = &depsv2ext.InstallationCriteria{
MatchInterface: dep.Installation.Criteria.MatchInterface,
MatchNamespace: dep.Installation.Criteria.MatchNamespace,
IgnoreLabels: dep.Installation.Criteria.IgnoreLabels,
}
}
}

if len(dep.Parameters) > 0 {
dependencyRef.Parameters = make(map[string]depsv2ext.DependencySource, len(dep.Parameters))
for param, source := range dep.Parameters {
ds, err := depsv2ext.ParseDependencySource(source)
if err != nil {
return nil, fmt.Errorf("invalid parameter wiring specified for dependency %s: %w", dep.Name, err)
}
dependencyRef.Parameters[param] = ds
}
}

if len(dep.Credentials) > 0 {
dependencyRef.Credentials = make(map[string]depsv2ext.DependencySource, len(dep.Credentials))
for cred, source := range dep.Credentials {
ds, err := depsv2ext.ParseDependencySource(source)
if err != nil {
return nil, fmt.Errorf("invalid credential wiring specified for dependency %s: %w", dep.Name, err)
}
dependencyRef.Credentials[cred] = ds
}
}

deps.Requires[dep.Name] = dependencyRef
}

return deps, nil
}

Expand Down Expand Up @@ -643,6 +716,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
79 changes: 53 additions & 26 deletions pkg/cnab/config-adapter/adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import (
"testing"

"get.porter.sh/porter/pkg/cnab"
depsv1 "get.porter.sh/porter/pkg/cnab/dependencies/v1"
depsv1ext "get.porter.sh/porter/pkg/cnab/extensions/dependencies/v1"
"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 +23,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.bundle.json"},
{name: "depsv2ext",
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)

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("testdata/mybuns.bundle.json", string(bunD))
// 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 @@ -538,24 +565,24 @@ func TestManifestConverter_generateDependencies(t *testing.T) {

testcases := []struct {
name string
wantDep depsv1.Dependency
wantDep depsv1ext.Dependency
}{
{"no-version", depsv1.Dependency{
{"no-version", depsv1ext.Dependency{
Name: "mysql",
Bundle: "getporter/azure-mysql:5.7",
}},
{"no-ranges, uses prerelease", depsv1.Dependency{
{"no-ranges, uses prerelease", depsv1ext.Dependency{
Name: "ad",
Bundle: "getporter/azure-active-directory",
Version: &depsv1.DependencyVersion{
Version: &depsv1ext.DependencyVersion{
AllowPrereleases: true,
Ranges: []string{"1.0.0-0"},
},
}},
{"with-ranges", depsv1.Dependency{
{"with-ranges", depsv1ext.Dependency{
Name: "storage",
Bundle: "getporter/azure-blob-storage",
Version: &depsv1.DependencyVersion{
Version: &depsv1ext.DependencyVersion{
Ranges: []string{
"1.x - 2,2.1 - 3.x",
},
Expand All @@ -581,12 +608,12 @@ func TestManifestConverter_generateDependencies(t *testing.T) {
depsExt, depsExtKey, err := a.generateDependencies()
require.NoError(t, err)
require.Equal(t, cnab.DependenciesV1ExtensionKey, depsExtKey, "expected the v1 dependencies extension key")
require.IsType(t, &depsv1.Dependencies{}, depsExt, "expected a v1 dependencies extension section")
deps := depsExt.(*depsv1.Dependencies)
require.IsType(t, &depsv1ext.Dependencies{}, depsExt, "expected a v1 dependencies extension section")
deps := depsExt.(*depsv1ext.Dependencies)
require.Len(t, deps.Requires, 3, "incorrect number of dependencies were generated")
require.Equal(t, []string{"mysql", "ad", "storage"}, deps.Sequence, "incorrect sequence was generated")

var dep *depsv1.Dependency
var dep *depsv1ext.Dependency
for _, d := range deps.Requires {
if d.Bundle == tc.wantDep.Bundle {
dep = &d
Expand Down
1 change: 1 addition & 0 deletions pkg/cnab/config-adapter/doc.go
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
// Package configadapter converts a Porter manifest (porter.yaml) to a CNAB bundle.json
package configadapter
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 242ca7e

Please sign in to comment.