diff --git a/pkg/cargo/validate.go b/pkg/cargo/validate.go index 94b2e751..3878f72d 100644 --- a/pkg/cargo/validate.go +++ b/pkg/cargo/validate.go @@ -2,9 +2,9 @@ package cargo import ( "fmt" - "slices" - "github.com/Masterminds/semver/v3" + "slices" + "text/template/parse" ) type ValidationOptions struct { @@ -75,6 +75,7 @@ func Validate(spec Kilnfile, lock KilnfileLock, options ...ValidationOptions) [] } result = append(result, ensureRemoteSourceExistsForEachReleaseLock(spec, lock)...) + result = append(result, ensureReleaseSourceConfiguration(spec.ReleaseSources)...) if len(result) > 0 { return result @@ -83,6 +84,64 @@ func Validate(spec Kilnfile, lock KilnfileLock, options ...ValidationOptions) [] 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 { diff --git a/pkg/cargo/validate_test.go b/pkg/cargo/validate_test.go index aa23fe2a..d9b41927 100644 --- a/pkg/cargo/validate_test.go +++ b/pkg/cargo/validate_test.go @@ -1,6 +1,7 @@ package cargo import ( + "github.com/stretchr/testify/require" "testing" . "github.com/onsi/gomega" @@ -326,4 +327,289 @@ func TestValidateWithOptions(t *testing.T) { } }) }) + + t.Run("when a release_source is not configured properly", func(t *testing.T) { + for _, tt := range []struct { + Name string + Sources []ReleaseSourceConfig + Error func(t *testing.T, errs []error) + }{ + { + Name: "artifactory host is empty", + Sources: []ReleaseSourceConfig{ + { + Type: BOSHReleaseTarballSourceTypeArtifactory, + ID: "", + // ArtifactoryHost: "http://example.com", + Username: "bot", + Password: "beep boop", + Repo: "secret-stash", + PathTemplate: "some-path", + }, + }, + Error: func(t *testing.T, errs []error) { + require.Len(t, errs, 1) + assert.ErrorContains(t, errs[0], "missing required field artifactory_host") + }, + }, + { + Name: "artifactory password is empty", + Sources: []ReleaseSourceConfig{ + { + Type: BOSHReleaseTarballSourceTypeArtifactory, + ID: "", + ArtifactoryHost: "http://example.com", + Username: "bot", + // Password: "beep boop", + Repo: "secret-stash", + PathTemplate: "some-path", + }, + }, + Error: func(t *testing.T, errs []error) { + require.Len(t, errs, 1) + assert.ErrorContains(t, errs[0], "missing required field password") + }, + }, + { + Name: "artifactory username is empty", + Sources: []ReleaseSourceConfig{ + { + Type: BOSHReleaseTarballSourceTypeArtifactory, + ID: "", + ArtifactoryHost: "http://example.com", + // Username: "bot", + Password: "beep boop", + Repo: "secret-stash", + PathTemplate: "some-path", + }, + }, + Error: func(t *testing.T, errs []error) { + require.Len(t, errs, 1) + assert.ErrorContains(t, errs[0], "missing required field username") + }, + }, + { + Name: "artifactory repo is empty", + Sources: []ReleaseSourceConfig{ + { + Type: BOSHReleaseTarballSourceTypeArtifactory, + ID: "", + ArtifactoryHost: "http://example.com", + Username: "bot", + Password: "beep boop", + // Repo: "secret-stash", + PathTemplate: "some-path", + }, + }, + Error: func(t *testing.T, errs []error) { + require.Len(t, errs, 1) + assert.ErrorContains(t, errs[0], "missing required field repo") + }, + }, + { + Name: "artifactory path_template is empty", + Sources: []ReleaseSourceConfig{ + { + Type: BOSHReleaseTarballSourceTypeArtifactory, + ID: "", + ArtifactoryHost: "http://example.com", + Username: "bot", + Password: "beep boop", + Repo: "secret-stash", + // PathTemplate: "some-path", + }, + }, + Error: func(t *testing.T, errs []error) { + require.Len(t, errs, 1) + assert.ErrorContains(t, errs[0], "missing required field path_template") + }, + }, + { + Name: "artifactory path_template is malformed", + Sources: []ReleaseSourceConfig{ + { + Type: BOSHReleaseTarballSourceTypeArtifactory, + ID: "", + ArtifactoryHost: "http://example.com", + Username: "bot", + Password: "beep boop", + Repo: "secret-stash", + PathTemplate: "{{ loosing power", + }, + }, + Error: func(t *testing.T, errs []error) { + require.Len(t, errs, 1) + assert.ErrorContains(t, errs[0], "failed to parse path_template:") + }, + }, + { + Name: "artifactory has unexpected field bucket", + Sources: []ReleaseSourceConfig{ + { + Type: BOSHReleaseTarballSourceTypeArtifactory, + ID: "", + ArtifactoryHost: "http://example.com", + Username: "bot", + Password: "beep boop", + Repo: "secret-stash", + PathTemplate: "ok", + + Bucket: "UNEXPECTED", + }, + }, + Error: func(t *testing.T, errs []error) { + require.Len(t, errs, 1) + assert.ErrorContains(t, errs[0], "unexpected field bucket") + }, + }, + { + Name: "artifactory has unexpected field region", + Sources: []ReleaseSourceConfig{ + { + Type: BOSHReleaseTarballSourceTypeArtifactory, + ID: "", + ArtifactoryHost: "http://example.com", + Username: "bot", + Password: "beep boop", + Repo: "secret-stash", + PathTemplate: "ok", + + Region: "UNEXPECTED", + }, + }, + Error: func(t *testing.T, errs []error) { + require.Len(t, errs, 1) + assert.ErrorContains(t, errs[0], "unexpected field region") + }, + }, + { + Name: "artifactory has unexpected field access_key_id", + Sources: []ReleaseSourceConfig{ + { + Type: BOSHReleaseTarballSourceTypeArtifactory, + ID: "", + ArtifactoryHost: "http://example.com", + Username: "bot", + Password: "beep boop", + Repo: "secret-stash", + PathTemplate: "ok", + + AccessKeyId: "UNEXPECTED", + }, + }, + Error: func(t *testing.T, errs []error) { + require.Len(t, errs, 1) + assert.ErrorContains(t, errs[0], "unexpected field access_key_id") + }, + }, + { + Name: "artifactory has unexpected field secret_access_key", + Sources: []ReleaseSourceConfig{ + { + Type: BOSHReleaseTarballSourceTypeArtifactory, + ID: "", + ArtifactoryHost: "http://example.com", + Username: "bot", + Password: "beep boop", + Repo: "secret-stash", + PathTemplate: "ok", + + SecretAccessKey: "UNEXPECTED", + }, + }, + Error: func(t *testing.T, errs []error) { + require.Len(t, errs, 1) + assert.ErrorContains(t, errs[0], "unexpected field secret_access_key") + }, + }, + { + Name: "artifactory has unexpected field role_arn", + Sources: []ReleaseSourceConfig{ + { + Type: BOSHReleaseTarballSourceTypeArtifactory, + ID: "", + ArtifactoryHost: "http://example.com", + Username: "bot", + Password: "beep boop", + Repo: "secret-stash", + PathTemplate: "ok", + + RoleARN: "UNEXPECTED", + }, + }, + Error: func(t *testing.T, errs []error) { + require.Len(t, errs, 1) + assert.ErrorContains(t, errs[0], "unexpected field role_arn") + }, + }, + { + Name: "artifactory has unexpected field endpoint", + Sources: []ReleaseSourceConfig{ + { + Type: BOSHReleaseTarballSourceTypeArtifactory, + ID: "", + ArtifactoryHost: "http://example.com", + Username: "bot", + Password: "beep boop", + Repo: "secret-stash", + PathTemplate: "ok", + + Endpoint: "UNEXPECTED", + }, + }, + Error: func(t *testing.T, errs []error) { + require.Len(t, errs, 1) + assert.ErrorContains(t, errs[0], "unexpected field endpoint") + }, + }, + { + Name: "artifactory has unexpected field org", + Sources: []ReleaseSourceConfig{ + { + Type: BOSHReleaseTarballSourceTypeArtifactory, + ID: "", + ArtifactoryHost: "http://example.com", + Username: "bot", + Password: "beep boop", + Repo: "secret-stash", + PathTemplate: "ok", + + Org: "UNEXPECTED", + }, + }, + Error: func(t *testing.T, errs []error) { + require.Len(t, errs, 1) + assert.ErrorContains(t, errs[0], "unexpected field org") + }, + }, + { + Name: "artifactory has unexpected field github_token", + Sources: []ReleaseSourceConfig{ + { + Type: BOSHReleaseTarballSourceTypeArtifactory, + ID: "", + ArtifactoryHost: "http://example.com", + Username: "bot", + Password: "beep boop", + Repo: "secret-stash", + PathTemplate: "ok", + + GithubToken: "UNEXPECTED", + }, + }, + Error: func(t *testing.T, errs []error) { + require.Len(t, errs, 1) + assert.ErrorContains(t, errs[0], "unexpected field github_token") + }, + }, + } { + t.Run(tt.Name, func(t *testing.T) { + k := Kilnfile{ + ReleaseSources: tt.Sources, + } + errs := Validate(k, KilnfileLock{}) + tt.Error(t, errs) + }) + } + }) }