Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kiln validate resource type allow list #501

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion internal/commands/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
type Validate struct {
Options struct {
flags.Standard
ReleaseSourceTypeAllowList []string `long:"allow-release-source-type"`
}

FS billy.Filesystem
Expand All @@ -38,7 +39,7 @@ func (v Validate) Execute(args []string) error {
return fmt.Errorf("failed to load kilnfiles: %w", err)
}

errs := cargo.Validate(kf, lock)
errs := cargo.Validate(kf, lock, cargo.ValidateResourceTypeAllowList(v.Options.ReleaseSourceTypeAllowList...))
if len(errs) > 0 {
return errorList(errs)
}
Expand Down
72 changes: 72 additions & 0 deletions internal/commands/validate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package commands_test

import (
"io"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

"github.com/go-git/go-billy/v5"
"github.com/go-git/go-billy/v5/memfs"

"github.com/pivotal-cf/kiln/internal/commands"
)

var _ = Describe("validate", func() {
var (
validate commands.Validate
directory billy.Filesystem
)

BeforeEach(func() {
directory = memfs.New()
})

JustBeforeEach(func() {
validate = commands.NewValidate(directory)
})

When("the kilnfile has two release_sources", func() {
BeforeEach(func() {
f, err := directory.Create("Kilnfile")
Expect(err).NotTo(HaveOccurred())
// language=yaml
_, _ = io.WriteString(f, `---
release_sources:
- type: "bosh.io"
- type: "github"
`)
_ = f.Close()
})

BeforeEach(func() {
f, err := directory.Create("Kilnfile.lock")
Expect(err).NotTo(HaveOccurred())
_ = f.Close()
})

When("both types are in the allow list", func() {
It("it does fail", func() {
err := validate.Execute([]string{
"--allow-release-source-type=bosh.io",
"--allow-release-source-type=github",
})
Expect(err).NotTo(HaveOccurred())
})
})
When("both one of the types is not in the allow list", func() {
It("it does fail", func() {
err := validate.Execute([]string{
"--allow-release-source-type=bosh.io",
})
Expect(err).To(MatchError(ContainSubstring("release source type not allowed: github")))
})
})
When("the allow list is empty", func() {
It("it does not fail", func() {
err := validate.Execute([]string{})
Expect(err).NotTo(HaveOccurred())
})
})
})
})
102 changes: 99 additions & 3 deletions pkg/cargo/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,51 @@ package cargo

import (
"fmt"
"slices"

"github.com/Masterminds/semver/v3"
"slices"
"text/template/parse"
)

func Validate(spec Kilnfile, lock KilnfileLock) []error {
type ValidationOptions struct {
resourceTypeAllowList []string
}

func NewValidateOptions() ValidationOptions {
return ValidationOptions{}
}

// ValidateResourceTypeAllowList calls ValidationOptions.SetValidateResourceTypeAllowList on the result of NewValidateOptions
func ValidateResourceTypeAllowList(allowList ...string) ValidationOptions {
return NewValidateOptions().SetValidateResourceTypeAllowList(allowList)
}

func (o ValidationOptions) SetValidateResourceTypeAllowList(allowList []string) ValidationOptions {
o.resourceTypeAllowList = allowList
return o
}

func mergeOptions(options []ValidationOptions) ValidationOptions {
var opt ValidationOptions
for _, o := range options {
if o.resourceTypeAllowList != nil {
opt.resourceTypeAllowList = o.resourceTypeAllowList
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we append to the list instead of replacing?

}
}
return opt
}

func Validate(spec Kilnfile, lock KilnfileLock, options ...ValidationOptions) []error {
opt := mergeOptions(options)
var result []error

if len(opt.resourceTypeAllowList) > 0 {
for _, s := range spec.ReleaseSources {
if !slices.Contains(opt.resourceTypeAllowList, s.Type) {
result = append(result, fmt.Errorf("release source type not allowed: %s", s.Type))
}
}
}

for index, componentSpec := range spec.Releases {
if componentSpec.Name == "" {
result = append(result, fmt.Errorf("release at index %d missing name in spec", index))
Expand Down Expand Up @@ -43,6 +80,7 @@ func Validate(spec Kilnfile, lock KilnfileLock) []error {
}

result = append(result, ensureRemoteSourceExistsForEachReleaseLock(spec, lock)...)
result = append(result, ensureReleaseSourceConfiguration(spec.ReleaseSources)...)

if len(result) > 0 {
return result
Expand All @@ -51,6 +89,64 @@ func Validate(spec Kilnfile, lock KilnfileLock) []error {
return nil
}

func ensureReleaseSourceConfiguration(sources []ReleaseSourceConfig) []error {
var errs []error
for _, source := range sources {
switch source.Type {
case BOSHReleaseTarballSourceTypeArtifactory:
if source.ArtifactoryHost == "" {
errs = append(errs, fmt.Errorf("missing required field artifactory_host"))
}
if source.Username == "" {
errs = append(errs, fmt.Errorf("missing required field username"))
}
if source.Password == "" {
errs = append(errs, fmt.Errorf("missing required field password"))
}
if source.Repo == "" {
errs = append(errs, fmt.Errorf("missing required field repo"))
}
if source.PathTemplate == "" {
errs = append(errs, fmt.Errorf("missing required field path_template"))
} else {
p := parse.New("path_template")
p.Mode |= parse.SkipFuncCheck
if _, err := p.Parse(source.PathTemplate, "", "", make(map[string]*parse.Tree)); err != nil {
errs = append(errs, fmt.Errorf("failed to parse path_template: %w", err))
}
}
if source.Bucket != "" {
errs = append(errs, fmt.Errorf("artifactory has unexpected field bucket"))
}
if source.Region != "" {
errs = append(errs, fmt.Errorf("artifactory has unexpected field region"))
}
if source.AccessKeyId != "" {
errs = append(errs, fmt.Errorf("artifactory has unexpected field access_key_id"))
}
if source.SecretAccessKey != "" {
errs = append(errs, fmt.Errorf("artifactory has unexpected field secret_access_key"))
}
if source.RoleARN != "" {
errs = append(errs, fmt.Errorf("artifactory has unexpected field role_arn"))
}
if source.Endpoint != "" {
errs = append(errs, fmt.Errorf("artifactory has unexpected field endpoint"))
}
if source.Org != "" {
errs = append(errs, fmt.Errorf("artifactory has unexpected field org"))
}
if source.GithubToken != "" {
errs = append(errs, fmt.Errorf("artifactory has unexpected field github_token"))
}
case BOSHReleaseTarballSourceTypeBOSHIO:
case BOSHReleaseTarballSourceTypeS3:
case BOSHReleaseTarballSourceTypeGithub:
}
}
return errs
}

func ensureRemoteSourceExistsForEachReleaseLock(spec Kilnfile, lock KilnfileLock) []error {
var result []error
for _, release := range lock.Releases {
Expand Down
Loading
Loading