Skip to content

Commit

Permalink
Added linting step and tests
Browse files Browse the repository at this point in the history
Signed-off-by: David Gannon <[email protected]>
  • Loading branch information
dgannon991 committed Sep 6, 2024
1 parent 2d9f5a9 commit 81acfa7
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 3 deletions.
38 changes: 38 additions & 0 deletions pkg/linter/linter.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"get.porter.sh/porter/pkg/portercontext"
"get.porter.sh/porter/pkg/tracing"
"get.porter.sh/porter/pkg/yaml"
"github.com/Masterminds/semver/v3"
"github.com/dustin/go-humanize"
)

Expand Down Expand Up @@ -264,9 +265,46 @@ func (l *Linter) Lint(ctx context.Context, m *manifest.Manifest, config *config.
results = append(results, r...)
}

span.Debug("Getting versions for each mixin used in the manifest...")
err = l.validateVersionNumberConstraints(ctx, m)
if err != nil {
return nil, span.Error(err)
}

return results, nil
}

func (l *Linter) validateVersionNumberConstraints(ctx context.Context, m *manifest.Manifest) error {
for _, mixin := range m.Mixins {
if mixin.Version != nil {
installedMeta, err := l.Mixins.GetMetadata(ctx, mixin.Name)
if err != nil {
return fmt.Errorf("unable to get metadata from mixin %s: %w", mixin.Name, err)
}
installedVersion := installedMeta.GetVersionInfo().Version

err = validateSemverConstraint(mixin.Name, installedVersion, mixin.Version)
if err != nil {
return err
}
}
}

return nil
}

func validateSemverConstraint(name string, installedVersion string, versionConstraint *semver.Constraints) error {
v, err := semver.NewVersion(installedVersion)
if err != nil {
return fmt.Errorf("invalid version number from mixin %s: %s. %w", name, installedVersion, err)
}

if !versionConstraint.Check(v) {
return fmt.Errorf("mixin %s is installed at version %s but your bundle requires version %s", name, installedVersion, versionConstraint)
}
return nil
}

func validateParamsAppliesToAction(m *manifest.Manifest, steps manifest.Steps, tmplParams manifest.ParameterDefinitions, actionName string, config *config.Config) (Results, error) {
var results Results
for stepNumber, step := range steps {
Expand Down
100 changes: 100 additions & 0 deletions pkg/linter/linter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import (
"get.porter.sh/porter/pkg/config"
"get.porter.sh/porter/pkg/manifest"
"get.porter.sh/porter/pkg/mixin"
"get.porter.sh/porter/pkg/pkgmgmt"
"get.porter.sh/porter/pkg/portercontext"
"get.porter.sh/porter/tests"
"github.com/Masterminds/semver/v3"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -363,3 +366,100 @@ func TestLinter_DependencyMultipleTimes(t *testing.T) {
require.Len(t, results, 0, "linter should have returned 0 result")
})
}

func TestLinter_Lint_MissingMixin(t *testing.T) {
cxt := portercontext.NewTestContext(t)
mixins := mixin.NewTestMixinProvider()
l := New(cxt.Context, mixins)
testConfig := config.NewTestConfig(t).Config

mixinName := "made-up-mixin-that-is-not-installed"

m := &manifest.Manifest{
Mixins: []manifest.MixinDeclaration{
{
Name: mixinName,
},
},
}

mixins.RunAssertions = append(mixins.RunAssertions, func(mixinCxt *portercontext.Context, mixinName string, commandOpts pkgmgmt.CommandOptions) error {
return fmt.Errorf("%s not installed", mixinName)
})

_, err := l.Lint(context.Background(), m, testConfig)
require.Error(t, err, "Linting should return an error")
tests.RequireOutputContains(t, err.Error(), fmt.Sprintf("%s is not currently installed", mixinName))
}

func TestLinter_Lint_MixinVersions(t *testing.T) {
cxt := portercontext.NewTestContext(t)
mixinProvider := mixin.NewTestMixinProvider()
l := New(cxt.Context, mixinProvider)
testConfig := config.NewTestConfig(t).Config

exampleMixinVersion := mixin.ExampleMixinSemver.String()

// build up some test semvers
patchDifferenceSemver := fmt.Sprintf("%d.%d.%d", mixin.ExampleMixinSemver.Major(), mixin.ExampleMixinSemver.Minor(), mixin.ExampleMixinSemver.Patch()+1)
anyPatchAccepted := fmt.Sprintf("%d.%d.x", mixin.ExampleMixinSemver.Major(), mixin.ExampleMixinSemver.Minor())
lessThanNextMajor := fmt.Sprintf("<%d.%d", mixin.ExampleMixinSemver.Major()+1, mixin.ExampleMixinSemver.Minor())

exampleMixinVersionConstraint, _ := semver.NewConstraint(exampleMixinVersion)
patchDifferenceSemverConstraint, _ := semver.NewConstraint(patchDifferenceSemver)
anyPatchAcceptedConstraint, _ := semver.NewConstraint(anyPatchAccepted)
lessThanNextMajorConstraint, _ := semver.NewConstraint(lessThanNextMajor)

testCases := []struct {
name string
errExpected bool
mixins []manifest.MixinDeclaration
}{
{"exact-semver", false, []manifest.MixinDeclaration{
{
Name: mixin.ExampleMixinName,
Version: exampleMixinVersionConstraint,
},
}},
{"different-patch", true, []manifest.MixinDeclaration{
{
Name: mixin.ExampleMixinName,
Version: patchDifferenceSemverConstraint,
},
}},
{"accept-different-patch", false, []manifest.MixinDeclaration{
{
Name: mixin.ExampleMixinName,
Version: anyPatchAcceptedConstraint,
},
}},
{"accept-less-than-versions", false, []manifest.MixinDeclaration{
{
Name: mixin.ExampleMixinName,
Version: lessThanNextMajorConstraint,
},
}},
}

for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
m := &manifest.Manifest{
Mixins: testCase.mixins,
}
results, err := l.Lint(context.Background(), m, testConfig)
if testCase.errExpected {
require.Error(t, err, "Linting should return an error")
tests.RequireOutputContains(t, err.Error(), fmt.Sprintf(
"mixin %s is installed at version v%s but your bundle requires version %s",
mixin.ExampleMixinName,
exampleMixinVersion,
testCase.mixins[0].Version.String(),
))
} else {
require.NoError(t, err, "Linting should not return an error")
}
require.Len(t, results, 0, "linter should have returned 0 result")
})
}

}
11 changes: 8 additions & 3 deletions pkg/mixin/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"get.porter.sh/porter/pkg/pkgmgmt"
"get.porter.sh/porter/pkg/pkgmgmt/client"
"get.porter.sh/porter/pkg/portercontext"
"github.com/Masterminds/semver/v3"
)

type TestMixinProvider struct {
Expand All @@ -24,6 +25,10 @@ type TestMixinProvider struct {
ReturnBuildError bool
}

// hoist these into variables so tests can reference them safely
var ExampleMixinName = "testmixin"
var ExampleMixinSemver = semver.New(0, 1, 0, "", "")

// NewTestMixinProvider helps us test Porter.Mixins in our unit tests without actually hitting any real plugins on the file system.
func NewTestMixinProvider() *TestMixinProvider {
packages := []pkgmgmt.PackageMetadata{
Expand All @@ -36,9 +41,9 @@ func NewTestMixinProvider() *TestMixinProvider {
},
},
&Metadata{
Name: "testmixin",
Name: ExampleMixinName,
VersionInfo: pkgmgmt.VersionInfo{
Version: "v0.1.0",
Version: fmt.Sprintf("v%s", ExampleMixinSemver.String()),
Commit: "abc123",
Author: "Porter Authors",
},
Expand Down Expand Up @@ -78,7 +83,7 @@ func (p *TestMixinProvider) GetSchema(ctx context.Context, name string) (string,
switch name {
case "exec":
schemaFile = "../exec/schema/exec.json"
case "testmixin":
case ExampleMixinName:
schemaFile = "../../cmd/testmixin/schema.json"
default:
return "", nil
Expand Down

0 comments on commit 81acfa7

Please sign in to comment.