From 9947668f4145654644d3c0ec94c560f8ea7a8646 Mon Sep 17 00:00:00 2001 From: Dmitry Kisler Date: Thu, 18 Jan 2024 00:33:50 +0100 Subject: [PATCH 01/13] feat: add validation for generated json metafiles. Signed-off-by: Dmitry Kisler --- src/cmd/validate/main.go | 149 +++++++++++++++++ src/cmd/validate/main_test.go | 292 ++++++++++++++++++++++++++++++++++ 2 files changed, 441 insertions(+) create mode 100644 src/cmd/validate/main.go create mode 100644 src/cmd/validate/main_test.go diff --git a/src/cmd/validate/main.go b/src/cmd/validate/main.go new file mode 100644 index 0000000000..1de4553797 --- /dev/null +++ b/src/cmd/validate/main.go @@ -0,0 +1,149 @@ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "log/slog" + "os" + + "github.com/opentofu/registry-stable/internal/module" + "github.com/opentofu/registry-stable/internal/provider" + "golang.org/x/mod/semver" +) + +func main() { + var logger = slog.New(slog.NewJSONHandler(os.Stdout, nil)) + + const ( + helpStr = `Run cmd/validate PATH/TO/definition.json + +CMD: +- module: validate module's registry JSON file. +- provider: validate provider's registry JSON file. +` + cliError = "command-line arguments don't match CLI signature" + ) + + args := os.Args[1:] + + if len(args) < 1 { + logger.Error(cliError) + os.Exit(1) + } + + if args[0] == "help" { + fmt.Print(helpStr) + os.Exit(0) + } + + if len(args) != 2 { + fmt.Print(helpStr) + logger.Error(cliError) + os.Exit(1) + } + + path := args[1] + + var err error + switch cmd := args[0]; cmd { + case "module": + err = validateModuleFile(path) + case "provider": + err = validateProviderFile(path) + + default: + fmt.Print(helpStr) + logger.Error("%s command is not supported", cmd) + os.Exit(1) + } + + if err != nil { + logger.Error("validation error", slog.Any("error", err)) + os.Exit(1) + } +} + +func readJSONFile(p string, v any) error { + f, err := os.Open(p) + if err != nil { + return err + } + defer f.Close() + + return json.NewDecoder(f).Decode(v) +} + +func validateModuleFile(p string) error { + var v module.Metadata + if err := readJSONFile(p, &v); err != nil { + return err + } + + if len(v.Versions) < 1 { + return EmptyList + } + + var errs = make([]error, 0, len(v.Versions)) + for _, ver := range v.Versions { + if !isValidVersion(ver.Version) { + err := fmt.Errorf("found semver-incompatible version: %s", ver.Version) + errs = append(errs, err) + } + } + + return errors.Join(errs...) +} + +func isValidVersion(ver string) bool { + if ver[0] != 'v' { + ver = "v" + ver + } + return semver.IsValid(ver) +} + +func validateProviderFile(p string) error { + var v provider.Metadata + if err := readJSONFile(p, &v); err != nil { + return err + } + + if len(v.Versions) < 1 { + return EmptyList + } + + var errs = make([]error, 0, len(v.Versions)) + for _, ver := range v.Versions { + if err := validateProviderVersion(ver); err != nil { + errs = append(errs, fmt.Errorf("invalid ver %s: %w", ver.Version, err)) + } + } + + return errors.Join(errs...) +} + +func validateProviderVersion(ver provider.Version) error { + var errs = make([]error, 0) + + if !isValidVersion(ver.Version) { + errs = append(errs, fmt.Errorf("found semver-incompatible version: %s", ver.Version)) + } + + // validate provider's protocols: + + // TODO: add explicit validation of the protocol version + if len(ver.Protocols) == 0 { + errs = append(errs, fmt.Errorf("empty protocols list")) + } + + // TODO: add validation of provider targets. Per target: + // - validate if os and arch are in the list of allowed + // - validate if the filename is consistent with the version, os and arch + // - validate if filename matches the url ending + + return errors.Join(errs...) +} + +var ( + EmptyList = errors.New("found empty list of versions") +) diff --git a/src/cmd/validate/main_test.go b/src/cmd/validate/main_test.go new file mode 100644 index 0000000000..37a68bcecf --- /dev/null +++ b/src/cmd/validate/main_test.go @@ -0,0 +1,292 @@ +package main + +import ( + "os" + "path" + "testing" +) + +func Test_validateModuleFile(t *testing.T) { + tests := map[string]struct { + fileContent []byte + wantErr bool + }{ + "valid-file": { + fileContent: []byte(`{ + "versions": [ + { + "version": "0.0.2" + }, + { + "version": "0.0.1" + } + ] +}`), + wantErr: false, + }, + "invalid-json": { + fileContent: []byte(`{ + "versions": [ + { + "version": "0.0.1" + }, + ] +}`), + wantErr: true, + }, + "invalid-version": { + fileContent: []byte(`{ + "versions": [ + { + "version": "0.0.1" + }, + { + "version": "foo" + } + ] +}`), + wantErr: true, + }, + "empty-versions-list": { + fileContent: []byte(`{"versions": []}`), + wantErr: true, + }, + "no-versions-attr-found": { + fileContent: []byte(`{}`), + wantErr: true, + }, + } + + t.Parallel() + + for name, tt := range tests { + dir := t.TempDir() + p := path.Join(dir, "module.json") + if err := os.WriteFile(p, tt.fileContent, 0774); err != nil { + t.Fatalf("cannot create temp file: %v", err) + } + + t.Run(name, func(t *testing.T) { + if err := validateModuleFile(p); (err != nil) != tt.wantErr { + t.Errorf("validateModuleFile() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_validateProviderFile(t *testing.T) { + tests := map[string]struct { + fileContent []byte + wantErr bool + }{ + "valid": { + fileContent: []byte(`{ + "versions": [ + { + "version": "0.0.2", + "protocols": [ + "6.0" + ], + "shasums_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS", + "shasums_signature_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS.sig", + "targets": [ + { + "os": "darwin", + "arch": "amd64", + "filename": "terraform-provider-metal_0.0.2_darwin_amd64.zip", + "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_darwin_amd64.zip", + "shasum": "0192019c49306991cf2413d937315a5a5aa83e8a3e01ec0891d3a8460422683a" + }, + { + "os": "darwin", + "arch": "arm64", + "filename": "terraform-provider-metal_0.0.2_darwin_arm64.zip", + "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_darwin_arm64.zip", + "shasum": "2da0c86252ff399567852412a8b750ca7ab0ffa48bd8e335edc0e8cec4e12195" + }, + { + "os": "freebsd", + "arch": "386", + "filename": "terraform-provider-metal_0.0.2_freebsd_386.zip", + "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_freebsd_386.zip", + "shasum": "50ad273e5f51c9b63d2e458b5266d9f50727a78d1db2d34940e3845744f0a141" + }, + { + "os": "freebsd", + "arch": "amd64", + "filename": "terraform-provider-metal_0.0.2_freebsd_amd64.zip", + "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_freebsd_amd64.zip", + "shasum": "83522ae20f82fddb11cbc12cb3f60829b27bcb71ddcb1b6c07728731afda5059" + }, + { + "os": "freebsd", + "arch": "arm", + "filename": "terraform-provider-metal_0.0.2_freebsd_arm.zip", + "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_freebsd_arm.zip", + "shasum": "7b181fa369d508aacb05db1b23441925b24fb4e3a6b68998d4d47e4f541651db" + }, + { + "os": "freebsd", + "arch": "arm64", + "filename": "terraform-provider-metal_0.0.2_freebsd_arm64.zip", + "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_freebsd_arm64.zip", + "shasum": "a213e541b786f4990af9d8a596877450f5a5e3deb593f566b5b977c3df74ae8f" + }, + { + "os": "linux", + "arch": "386", + "filename": "terraform-provider-metal_0.0.2_linux_386.zip", + "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_linux_386.zip", + "shasum": "d2eea09e5c1607691e003db950ffc79274d791bf190c3b246fa041dbe7c15a78" + }, + { + "os": "linux", + "arch": "amd64", + "filename": "terraform-provider-metal_0.0.2_linux_amd64.zip", + "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_linux_amd64.zip", + "shasum": "8452e035c92bf319a76e1dac8df11147f550affc85017237ae5cc22c9254bd1f" + }, + { + "os": "linux", + "arch": "arm", + "filename": "terraform-provider-metal_0.0.2_linux_arm.zip", + "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_linux_arm.zip", + "shasum": "facb0f30c4b9171c42ac0995d6e99b4cf0dfa788ada353b06436afcecf72e412" + }, + { + "os": "linux", + "arch": "arm64", + "filename": "terraform-provider-metal_0.0.2_linux_arm64.zip", + "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_linux_arm64.zip", + "shasum": "6c17811307361f07919514efc0b2eb7637a9c9f580d7d325871002ae23ea8897" + }, + { + "os": "windows", + "arch": "386", + "filename": "terraform-provider-metal_0.0.2_windows_386.zip", + "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_windows_386.zip", + "shasum": "e31b3495ac451450b1ba3ff96b0e3bde1f79d2d2b0ae6f1166cbe2418b2ac70a" + }, + { + "os": "windows", + "arch": "amd64", + "filename": "terraform-provider-metal_0.0.2_windows_amd64.zip", + "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_windows_amd64.zip", + "shasum": "329e8fcc52e716050f2a153a90f4da7e42e40ddb71a8d593d364db181f7f3cac" + }, + { + "os": "windows", + "arch": "arm", + "filename": "terraform-provider-metal_0.0.2_windows_arm.zip", + "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_windows_arm.zip", + "shasum": "d3866da93888cf2a6f38b4bf04f8a6eef8bb40fb639f9ef275afa38e39fac466" + }, + { + "os": "windows", + "arch": "arm64", + "filename": "terraform-provider-metal_0.0.2_windows_arm64.zip", + "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_windows_arm64.zip", + "shasum": "92b428195b230b7dd0ad4300138a6e5a65dc6e87376f0799436158f7943270cf" + } + ] + } + ] +}`), + wantErr: false, + }, + "empty-versions-list": { + fileContent: []byte(`{"versions": []}`), + wantErr: true, + }, + "no-versions-attr-found": { + fileContent: []byte(`{}`), + wantErr: true, + }, + "invalid-version": { + fileContent: []byte(`{ + "versions": [ + { + "version": "foo", + "protocols": [ + "6.0" + ], + "shasums_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/vfoo/terraform-provider-metal_foo_SHA256SUMS", + "shasums_signature_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/vfoo/terraform-provider-metal_foo_SHA256SUMS.sig", + "targets": [ + { + "os": "darwin", + "arch": "amd64", + "filename": "terraform-provider-metal_foo_darwin_amd64.zip", + "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/vfoo/terraform-provider-metal_foo_darwin_amd64.zip", + "shasum": "0192019c49306991cf2413d937315a5a5aa83e8a3e01ec0891d3a8460422683a" + } + ] + } + ] +} +`), + wantErr: true, + }, + "empty-protocols-list": { + fileContent: []byte(`{ + "versions": [ + { + "version": "0.0.2", + "protocols": [], + "shasums_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS", + "shasums_signature_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS.sig", + "targets": [ + { + "os": "darwin", + "arch": "amd64", + "filename": "terraform-provider-metal_0.0.2_darwin_amd64.zip", + "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_darwin_amd64.zip", + "shasum": "0192019c49306991cf2413d937315a5a5aa83e8a3e01ec0891d3a8460422683a" + } + ] + } + ] +} +`), + wantErr: true, + }, + "no-protocols-attr-found": { + fileContent: []byte(`{ + "versions": [ + { + "version": "0.0.2", + "shasums_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS", + "shasums_signature_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS.sig", + "targets": [ + { + "os": "darwin", + "arch": "amd64", + "filename": "terraform-provider-metal_0.0.2_darwin_amd64.zip", + "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_darwin_amd64.zip", + "shasum": "0192019c49306991cf2413d937315a5a5aa83e8a3e01ec0891d3a8460422683a" + } + ] + } + ] +} +`), + wantErr: true, + }, + } + + t.Parallel() + + for name, tt := range tests { + dir := t.TempDir() + p := path.Join(dir, "module.json") + if err := os.WriteFile(p, tt.fileContent, 0774); err != nil { + t.Fatalf("cannot create temp file: %v", err) + } + + t.Run(name, func(t *testing.T) { + if err := validateProviderFile(p); (err != nil) != tt.wantErr { + t.Errorf("validateProviderFile() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} From a0117981be096084d4b1536e3574615e59b022aa Mon Sep 17 00:00:00 2001 From: Dmitry Kisler Date: Tue, 30 Jan 2024 13:58:13 +0100 Subject: [PATCH 02/13] Add actions for module validation Signed-off-by: Dmitry Kisler --- .github/workflows/validate-json-module.yml | 28 ++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/validate-json-module.yml diff --git a/.github/workflows/validate-json-module.yml b/.github/workflows/validate-json-module.yml new file mode 100644 index 0000000000..d995f901a6 --- /dev/null +++ b/.github/workflows/validate-json-module.yml @@ -0,0 +1,28 @@ +name: Validate Module JSON + +on: + push: + paths: + - 'modules/**/*.json' + branches: + - 'module-**' + +jobs: + validate-metadata-module: + runs-on: ubuntu-latest + environment: + name: ${{ inputs.environment }} + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v4 + with: + go-version-file: './src/go.mod' + + - name: Validate JSON + working-directory: ./src + run: | + for path in $(git diff HEAD~1 HEAD --name-only) + do + go run ./cmd/validate/main.go module $path + done From ec502a8135fe775f11e9af2d037e5908428138eb Mon Sep 17 00:00:00 2001 From: Dmitry Kisler Date: Tue, 30 Jan 2024 14:12:07 +0100 Subject: [PATCH 03/13] Add actions for provider validation Signed-off-by: Dmitry Kisler --- .github/workflows/validate-json-provider.yml | 28 ++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/validate-json-provider.yml diff --git a/.github/workflows/validate-json-provider.yml b/.github/workflows/validate-json-provider.yml new file mode 100644 index 0000000000..b1188baab9 --- /dev/null +++ b/.github/workflows/validate-json-provider.yml @@ -0,0 +1,28 @@ +name: Validate Provider JSON + +on: + push: + paths: + - 'providers/**/*.json' + branches: + - 'provider-**' + +jobs: + validate-metadata-module: + runs-on: ubuntu-latest + environment: + name: ${{ inputs.environment }} + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v4 + with: + go-version-file: './src/go.mod' + + - name: Validate JSON + working-directory: ./src + run: | + for path in $(git diff HEAD~1 HEAD --name-only) + do + go run ./cmd/validate/main.go provider $path + done From 9b420b9e9b6eabca9f8b6cc5869a8203175cf70d Mon Sep 17 00:00:00 2001 From: Dmitry Kisler Date: Tue, 30 Jan 2024 16:45:51 +0100 Subject: [PATCH 04/13] Refactor validation to be collocated within the provider and module packages. Signed-off-by: Dmitry Kisler --- src/cmd/validate/main.go | 107 ++---- src/cmd/validate/main_test.go | 292 ---------------- src/internal/module/validate.go | 25 ++ src/internal/module/validate_test.go | 42 +++ src/internal/provider/build.go | 2 +- src/internal/provider/update.go | 6 +- src/internal/provider/validate.go | 97 ++++++ src/internal/provider/validate_test.go | 406 ++++++++++++++++++++++ src/internal/validate/errors.go | 5 + src/internal/validate/validate-version.go | 11 + 10 files changed, 616 insertions(+), 377 deletions(-) delete mode 100644 src/cmd/validate/main_test.go create mode 100644 src/internal/module/validate.go create mode 100644 src/internal/module/validate_test.go create mode 100644 src/internal/provider/validate.go create mode 100644 src/internal/provider/validate_test.go create mode 100644 src/internal/validate/errors.go create mode 100644 src/internal/validate/validate-version.go diff --git a/src/cmd/validate/main.go b/src/cmd/validate/main.go index 1de4553797..d0364a3862 100644 --- a/src/cmd/validate/main.go +++ b/src/cmd/validate/main.go @@ -2,14 +2,12 @@ package main import ( "encoding/json" - "errors" "fmt" "log/slog" "os" "github.com/opentofu/registry-stable/internal/module" "github.com/opentofu/registry-stable/internal/provider" - "golang.org/x/mod/semver" ) func main() { @@ -28,7 +26,7 @@ CMD: args := os.Args[1:] if len(args) < 1 { - logger.Error(cliError) + logger.Error(errorCLI, slog.String("error", cliError)) os.Exit(1) } @@ -39,27 +37,42 @@ CMD: if len(args) != 2 { fmt.Print(helpStr) - logger.Error(cliError) + logger.Error(errorCLI, slog.String("error", cliError)) os.Exit(1) } path := args[1] - var err error + var ( + err error + errType string = errorJSONParsing + ) switch cmd := args[0]; cmd { case "module": - err = validateModuleFile(path) + var v module.Metadata + err = readJSONFile(path, &v) + if err == nil { + errType = errorValidation + err = module.Validate(v) + } + case "provider": - err = validateProviderFile(path) + var v provider.Metadata + err = readJSONFile(path, &v) + if err == nil { + errType = errorValidation + err = provider.Validate(v) + } default: fmt.Print(helpStr) - logger.Error("%s command is not supported", cmd) + logger.Error(errorCLI, slog.String("error", fmt.Sprintf("%s command is not supported", cmd))) os.Exit(1) } if err != nil { - logger.Error("validation error", slog.Any("error", err)) + logger.Error(errType) + fmt.Println(err) os.Exit(1) } } @@ -74,76 +87,8 @@ func readJSONFile(p string, v any) error { return json.NewDecoder(f).Decode(v) } -func validateModuleFile(p string) error { - var v module.Metadata - if err := readJSONFile(p, &v); err != nil { - return err - } - - if len(v.Versions) < 1 { - return EmptyList - } - - var errs = make([]error, 0, len(v.Versions)) - for _, ver := range v.Versions { - if !isValidVersion(ver.Version) { - err := fmt.Errorf("found semver-incompatible version: %s", ver.Version) - errs = append(errs, err) - } - } - - return errors.Join(errs...) -} - -func isValidVersion(ver string) bool { - if ver[0] != 'v' { - ver = "v" + ver - } - return semver.IsValid(ver) -} - -func validateProviderFile(p string) error { - var v provider.Metadata - if err := readJSONFile(p, &v); err != nil { - return err - } - - if len(v.Versions) < 1 { - return EmptyList - } - - var errs = make([]error, 0, len(v.Versions)) - for _, ver := range v.Versions { - if err := validateProviderVersion(ver); err != nil { - errs = append(errs, fmt.Errorf("invalid ver %s: %w", ver.Version, err)) - } - } - - return errors.Join(errs...) -} - -func validateProviderVersion(ver provider.Version) error { - var errs = make([]error, 0) - - if !isValidVersion(ver.Version) { - errs = append(errs, fmt.Errorf("found semver-incompatible version: %s", ver.Version)) - } - - // validate provider's protocols: - - // TODO: add explicit validation of the protocol version - if len(ver.Protocols) == 0 { - errs = append(errs, fmt.Errorf("empty protocols list")) - } - - // TODO: add validation of provider targets. Per target: - // - validate if os and arch are in the list of allowed - // - validate if the filename is consistent with the version, os and arch - // - validate if filename matches the url ending - - return errors.Join(errs...) -} - -var ( - EmptyList = errors.New("found empty list of versions") +const ( + errorValidation = "validation error" + errorCLI = "CLI error" + errorJSONParsing = "JSON parsing error" ) diff --git a/src/cmd/validate/main_test.go b/src/cmd/validate/main_test.go deleted file mode 100644 index 37a68bcecf..0000000000 --- a/src/cmd/validate/main_test.go +++ /dev/null @@ -1,292 +0,0 @@ -package main - -import ( - "os" - "path" - "testing" -) - -func Test_validateModuleFile(t *testing.T) { - tests := map[string]struct { - fileContent []byte - wantErr bool - }{ - "valid-file": { - fileContent: []byte(`{ - "versions": [ - { - "version": "0.0.2" - }, - { - "version": "0.0.1" - } - ] -}`), - wantErr: false, - }, - "invalid-json": { - fileContent: []byte(`{ - "versions": [ - { - "version": "0.0.1" - }, - ] -}`), - wantErr: true, - }, - "invalid-version": { - fileContent: []byte(`{ - "versions": [ - { - "version": "0.0.1" - }, - { - "version": "foo" - } - ] -}`), - wantErr: true, - }, - "empty-versions-list": { - fileContent: []byte(`{"versions": []}`), - wantErr: true, - }, - "no-versions-attr-found": { - fileContent: []byte(`{}`), - wantErr: true, - }, - } - - t.Parallel() - - for name, tt := range tests { - dir := t.TempDir() - p := path.Join(dir, "module.json") - if err := os.WriteFile(p, tt.fileContent, 0774); err != nil { - t.Fatalf("cannot create temp file: %v", err) - } - - t.Run(name, func(t *testing.T) { - if err := validateModuleFile(p); (err != nil) != tt.wantErr { - t.Errorf("validateModuleFile() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func Test_validateProviderFile(t *testing.T) { - tests := map[string]struct { - fileContent []byte - wantErr bool - }{ - "valid": { - fileContent: []byte(`{ - "versions": [ - { - "version": "0.0.2", - "protocols": [ - "6.0" - ], - "shasums_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS", - "shasums_signature_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS.sig", - "targets": [ - { - "os": "darwin", - "arch": "amd64", - "filename": "terraform-provider-metal_0.0.2_darwin_amd64.zip", - "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_darwin_amd64.zip", - "shasum": "0192019c49306991cf2413d937315a5a5aa83e8a3e01ec0891d3a8460422683a" - }, - { - "os": "darwin", - "arch": "arm64", - "filename": "terraform-provider-metal_0.0.2_darwin_arm64.zip", - "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_darwin_arm64.zip", - "shasum": "2da0c86252ff399567852412a8b750ca7ab0ffa48bd8e335edc0e8cec4e12195" - }, - { - "os": "freebsd", - "arch": "386", - "filename": "terraform-provider-metal_0.0.2_freebsd_386.zip", - "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_freebsd_386.zip", - "shasum": "50ad273e5f51c9b63d2e458b5266d9f50727a78d1db2d34940e3845744f0a141" - }, - { - "os": "freebsd", - "arch": "amd64", - "filename": "terraform-provider-metal_0.0.2_freebsd_amd64.zip", - "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_freebsd_amd64.zip", - "shasum": "83522ae20f82fddb11cbc12cb3f60829b27bcb71ddcb1b6c07728731afda5059" - }, - { - "os": "freebsd", - "arch": "arm", - "filename": "terraform-provider-metal_0.0.2_freebsd_arm.zip", - "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_freebsd_arm.zip", - "shasum": "7b181fa369d508aacb05db1b23441925b24fb4e3a6b68998d4d47e4f541651db" - }, - { - "os": "freebsd", - "arch": "arm64", - "filename": "terraform-provider-metal_0.0.2_freebsd_arm64.zip", - "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_freebsd_arm64.zip", - "shasum": "a213e541b786f4990af9d8a596877450f5a5e3deb593f566b5b977c3df74ae8f" - }, - { - "os": "linux", - "arch": "386", - "filename": "terraform-provider-metal_0.0.2_linux_386.zip", - "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_linux_386.zip", - "shasum": "d2eea09e5c1607691e003db950ffc79274d791bf190c3b246fa041dbe7c15a78" - }, - { - "os": "linux", - "arch": "amd64", - "filename": "terraform-provider-metal_0.0.2_linux_amd64.zip", - "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_linux_amd64.zip", - "shasum": "8452e035c92bf319a76e1dac8df11147f550affc85017237ae5cc22c9254bd1f" - }, - { - "os": "linux", - "arch": "arm", - "filename": "terraform-provider-metal_0.0.2_linux_arm.zip", - "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_linux_arm.zip", - "shasum": "facb0f30c4b9171c42ac0995d6e99b4cf0dfa788ada353b06436afcecf72e412" - }, - { - "os": "linux", - "arch": "arm64", - "filename": "terraform-provider-metal_0.0.2_linux_arm64.zip", - "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_linux_arm64.zip", - "shasum": "6c17811307361f07919514efc0b2eb7637a9c9f580d7d325871002ae23ea8897" - }, - { - "os": "windows", - "arch": "386", - "filename": "terraform-provider-metal_0.0.2_windows_386.zip", - "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_windows_386.zip", - "shasum": "e31b3495ac451450b1ba3ff96b0e3bde1f79d2d2b0ae6f1166cbe2418b2ac70a" - }, - { - "os": "windows", - "arch": "amd64", - "filename": "terraform-provider-metal_0.0.2_windows_amd64.zip", - "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_windows_amd64.zip", - "shasum": "329e8fcc52e716050f2a153a90f4da7e42e40ddb71a8d593d364db181f7f3cac" - }, - { - "os": "windows", - "arch": "arm", - "filename": "terraform-provider-metal_0.0.2_windows_arm.zip", - "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_windows_arm.zip", - "shasum": "d3866da93888cf2a6f38b4bf04f8a6eef8bb40fb639f9ef275afa38e39fac466" - }, - { - "os": "windows", - "arch": "arm64", - "filename": "terraform-provider-metal_0.0.2_windows_arm64.zip", - "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_windows_arm64.zip", - "shasum": "92b428195b230b7dd0ad4300138a6e5a65dc6e87376f0799436158f7943270cf" - } - ] - } - ] -}`), - wantErr: false, - }, - "empty-versions-list": { - fileContent: []byte(`{"versions": []}`), - wantErr: true, - }, - "no-versions-attr-found": { - fileContent: []byte(`{}`), - wantErr: true, - }, - "invalid-version": { - fileContent: []byte(`{ - "versions": [ - { - "version": "foo", - "protocols": [ - "6.0" - ], - "shasums_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/vfoo/terraform-provider-metal_foo_SHA256SUMS", - "shasums_signature_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/vfoo/terraform-provider-metal_foo_SHA256SUMS.sig", - "targets": [ - { - "os": "darwin", - "arch": "amd64", - "filename": "terraform-provider-metal_foo_darwin_amd64.zip", - "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/vfoo/terraform-provider-metal_foo_darwin_amd64.zip", - "shasum": "0192019c49306991cf2413d937315a5a5aa83e8a3e01ec0891d3a8460422683a" - } - ] - } - ] -} -`), - wantErr: true, - }, - "empty-protocols-list": { - fileContent: []byte(`{ - "versions": [ - { - "version": "0.0.2", - "protocols": [], - "shasums_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS", - "shasums_signature_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS.sig", - "targets": [ - { - "os": "darwin", - "arch": "amd64", - "filename": "terraform-provider-metal_0.0.2_darwin_amd64.zip", - "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_darwin_amd64.zip", - "shasum": "0192019c49306991cf2413d937315a5a5aa83e8a3e01ec0891d3a8460422683a" - } - ] - } - ] -} -`), - wantErr: true, - }, - "no-protocols-attr-found": { - fileContent: []byte(`{ - "versions": [ - { - "version": "0.0.2", - "shasums_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS", - "shasums_signature_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS.sig", - "targets": [ - { - "os": "darwin", - "arch": "amd64", - "filename": "terraform-provider-metal_0.0.2_darwin_amd64.zip", - "download_url": "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_darwin_amd64.zip", - "shasum": "0192019c49306991cf2413d937315a5a5aa83e8a3e01ec0891d3a8460422683a" - } - ] - } - ] -} -`), - wantErr: true, - }, - } - - t.Parallel() - - for name, tt := range tests { - dir := t.TempDir() - p := path.Join(dir, "module.json") - if err := os.WriteFile(p, tt.fileContent, 0774); err != nil { - t.Fatalf("cannot create temp file: %v", err) - } - - t.Run(name, func(t *testing.T) { - if err := validateProviderFile(p); (err != nil) != tt.wantErr { - t.Errorf("validateProviderFile() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/src/internal/module/validate.go b/src/internal/module/validate.go new file mode 100644 index 0000000000..b72e1c2fd0 --- /dev/null +++ b/src/internal/module/validate.go @@ -0,0 +1,25 @@ +package module + +import ( + "errors" + "fmt" + + "github.com/opentofu/registry-stable/internal/validate" +) + +// Validate validates module's metadata. +func Validate(v Metadata) error { + if len(v.Versions) < 1 { + return validate.ErrorEmptyList + } + + var errs = make([]error, 0, len(v.Versions)) + for _, ver := range v.Versions { + if !validate.IsValidVersion(ver.Version) { + err := fmt.Errorf("found semver-incompatible version: %s", ver.Version) + errs = append(errs, err) + } + } + + return errors.Join(errs...) +} diff --git a/src/internal/module/validate_test.go b/src/internal/module/validate_test.go new file mode 100644 index 0000000000..a0026cd795 --- /dev/null +++ b/src/internal/module/validate_test.go @@ -0,0 +1,42 @@ +package module + +import ( + "testing" +) + +func TestValidate(t *testing.T) { + type TestCase struct { + name string + input Metadata + wantErr bool + } + + tests := []TestCase{ + { + name: "valid", + input: Metadata{ + Versions: []Version{{"0.0.2"}, {"0.0.1"}}, + }, + }, + { + name: "invalid-version", + input: Metadata{ + Versions: []Version{{"0.0.2"}, {"foo"}}, + }, + wantErr: true, + }, + { + name: "empty-versions-list", + input: Metadata{}, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := Validate(tt.input); (err != nil) != tt.wantErr { + t.Fatalf("Validate(%v) unexpected error = %v", tt.input, err) + } + }) + } +} diff --git a/src/internal/provider/build.go b/src/internal/provider/build.go index ef99a3caec..350070ba35 100644 --- a/src/internal/provider/build.go +++ b/src/internal/provider/build.go @@ -31,7 +31,7 @@ func (meta Metadata) filterNewReleases(releases []string) []string { return newReleases } -// getSemverTags returns a list of semver tags for the module fetched from GitHub. +// getSemverTags returns a list of validate.go tags for the module fetched from GitHub. func (p Provider) getSemverTags() ([]string, error) { tags, err := p.Github.GetTags(p.RepositoryURL()) if err != nil { diff --git a/src/internal/provider/update.go b/src/internal/provider/update.go index 5b30ef75c6..358ba6db92 100644 --- a/src/internal/provider/update.go +++ b/src/internal/provider/update.go @@ -52,8 +52,8 @@ func (p Provider) shouldUpdateMetadataFile() (bool, error) { return true, nil } -// getSemVerTagsFromRSS returns a list of semver tags from the RSS feed -// ignoring all non-valid semver tags +// getSemVerTagsFromRSS returns a list of validate.go tags from the RSS feed +// ignoring all non-valid validate.go tags func (p Provider) getSemVerTagsFromRSS() ([]string, error) { releasesRssUrl := p.RSSURL() tags, err := p.Github.GetTagsFromRSS(releasesRssUrl) @@ -71,7 +71,7 @@ func (p Provider) getSemVerTagsFromRSS() ([]string, error) { return semverTags, nil } -// getLastSemVerTag returns the most recently created semver tag from the RSS feed +// getLastSemVerTag returns the most recently created validate.go tag from the RSS feed // by sorting the tags by descending creation date func (p Provider) getLastSemVerTag() (string, error) { semverTags, err := p.getSemVerTagsFromRSS() diff --git a/src/internal/provider/validate.go b/src/internal/provider/validate.go new file mode 100644 index 0000000000..37d1b53864 --- /dev/null +++ b/src/internal/provider/validate.go @@ -0,0 +1,97 @@ +package provider + +import ( + "errors" + "fmt" + "slices" + "strings" + + "github.com/opentofu/registry-stable/internal/validate" +) + +// Validate validates provider's metadata. +func Validate(v Metadata) error { + if len(v.Versions) < 1 { + return validate.ErrorEmptyList + } + + var errs = make([]error, 0, len(v.Versions)) + for _, ver := range v.Versions { + for _, err := range validateProviderVersion(ver) { + errs = append(errs, fmt.Errorf("v%s: %w", ver.Version, err)) + } + } + + return errors.Join(errs...) +} + +func validateProviderVersion(ver Version) []error { + var errs = make([]error, 0) + + if !validate.IsValidVersion(ver.Version) { + errs = append(errs, fmt.Errorf("found semver-incompatible version: %s", ver.Version)) + } + + // validates provider's protocols: + if len(ver.Protocols) == 0 { + errs = append(errs, fmt.Errorf("empty protocols list")) + } else { + for _, protocol := range ver.Protocols { + if !isValidProviderProtocol(protocol) { + errs = append(errs, fmt.Errorf("unsupported protocol found: %s", protocol)) + } + } + } + + // validates provider's targets: + if len(ver.Targets) == 0 { + errs = append(errs, fmt.Errorf("empty targets list")) + } else { + for _, verTarget := range ver.Targets { + if err := validateProviderVersionTarget(verTarget); err != nil { + errs = append(errs, err...) + } + } + } + + return errs +} + +// isValidProviderProtocol validates the protocol version. +// It's based on the providers which are currently available in the registry. +func isValidProviderProtocol(s string) bool { + switch s { + case "1.0", + "1.0.0", + "4.0", + "5.0", + "6.0": + return true + default: + return false + } +} + +func validateProviderVersionTarget(v Target) []error { + var errs = make([]error, 0) + + if !slices.Contains(goos, v.OS) { + errs = append(errs, fmt.Errorf("target %s-%s: unsupported OS: %s", v.OS, v.Arch, v.OS)) + } + + if !slices.Contains(goarch, v.Arch) { + errs = append(errs, fmt.Errorf("target %s-%s: unsupported ARCH: %s", v.OS, v.Arch, v.Arch)) + } + + // check if the filename matches the url + if !strings.HasSuffix(v.DownloadURL, v.Filename) { + errs = append(errs, fmt.Errorf("target %s-%s: 'filename' is not consistent with 'download_url'", v.OS, v.Arch)) + } + + // check if the SHA sum was modified + if len(v.SHASum) != 64 { + errs = append(errs, fmt.Errorf("target %s-%s: SHASum length is wrong", v.OS, v.Arch)) + } + + return errs +} diff --git a/src/internal/provider/validate_test.go b/src/internal/provider/validate_test.go new file mode 100644 index 0000000000..8afabdf91c --- /dev/null +++ b/src/internal/provider/validate_test.go @@ -0,0 +1,406 @@ +package provider + +import ( + "testing" +) + +func TestValidate(t *testing.T) { + type TestCase struct { + name string + input Metadata + wantErrStr string + } + tests := []TestCase{ + { + name: "valid", + input: Metadata{ + Versions: []Version{ + { + Version: "0.0.2", + Protocols: []string{"6.0"}, + SHASumsURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS", + SHASumsSignatureURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS.sig", + Targets: []Target{ + { + OS: "darwin", + Arch: "amd64", + Filename: "terraform-provider-metal_0.0.2_darwin_amd64.zip", + DownloadURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_darwin_amd64.zip", + SHASum: "0192019c49306991cf2413d937315a5a5aa83e8a3e01ec0891d3a8460422683a", + }, + { + OS: "darwin", + Arch: "arm64", + Filename: "terraform-provider-metal_0.0.2_darwin_arm64.zip", + DownloadURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_darwin_arm64.zip", + SHASum: "2da0c86252ff399567852412a8b750ca7ab0ffa48bd8e335edc0e8cec4e12195", + }, + { + OS: "linux", + Arch: "arm64", + Filename: "terraform-provider-metal_0.0.2_linux_arm64.zip", + DownloadURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_linux_arm64.zip", + SHASum: "6c17811307361f07919514efc0b2eb7637a9c9f580d7d325871002ae23ea8897", + }, + }, + }, + }, + }, + wantErrStr: "", + }, + { + name: "no versions data", + input: Metadata{}, + wantErrStr: "found empty list of versions", + }, + { + name: "invalid version", + input: Metadata{ + Versions: []Version{ + { + Version: "foo", + Protocols: []string{"6.0"}, + SHASumsURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS", + SHASumsSignatureURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS.sig", + Targets: []Target{ + { + OS: "linux", + Arch: "arm64", + Filename: "terraform-provider-metal_0.0.2_linux_arm64.zip", + DownloadURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_linux_arm64.zip", + SHASum: "6c17811307361f07919514efc0b2eb7637a9c9f580d7d325871002ae23ea8897", + }, + }, + }, + { + Version: "0.0.2", + Protocols: []string{"6.0"}, + SHASumsURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS", + SHASumsSignatureURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS.sig", + Targets: []Target{ + { + OS: "darwin", + Arch: "amd64", + Filename: "terraform-provider-metal_0.0.2_darwin_amd64.zip", + DownloadURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_darwin_amd64.zip", + SHASum: "0192019c49306991cf2413d937315a5a5aa83e8a3e01ec0891d3a8460422683a", + }, + { + OS: "darwin", + Arch: "arm64", + Filename: "terraform-provider-metal_0.0.2_darwin_arm64.zip", + DownloadURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_darwin_arm64.zip", + SHASum: "2da0c86252ff399567852412a8b750ca7ab0ffa48bd8e335edc0e8cec4e12195", + }, + { + OS: "linux", + Arch: "arm64", + Filename: "terraform-provider-metal_0.0.2_linux_arm64.zip", + DownloadURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_linux_arm64.zip", + SHASum: "6c17811307361f07919514efc0b2eb7637a9c9f580d7d325871002ae23ea8897", + }, + }, + }, + }, + }, + wantErrStr: "vfoo: found semver-incompatible version: foo", + }, + { + name: "no protocols", + input: Metadata{ + Versions: []Version{ + { + Version: "0.0.2", + SHASumsURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS", + SHASumsSignatureURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS.sig", + Targets: []Target{ + { + OS: "darwin", + Arch: "amd64", + Filename: "terraform-provider-metal_0.0.2_darwin_amd64.zip", + DownloadURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_darwin_amd64.zip", + SHASum: "0192019c49306991cf2413d937315a5a5aa83e8a3e01ec0891d3a8460422683a", + }, + { + OS: "darwin", + Arch: "arm64", + Filename: "terraform-provider-metal_0.0.2_darwin_arm64.zip", + DownloadURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_darwin_arm64.zip", + SHASum: "2da0c86252ff399567852412a8b750ca7ab0ffa48bd8e335edc0e8cec4e12195", + }, + { + OS: "linux", + Arch: "arm64", + Filename: "terraform-provider-metal_0.0.2_linux_arm64.zip", + DownloadURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_linux_arm64.zip", + SHASum: "6c17811307361f07919514efc0b2eb7637a9c9f580d7d325871002ae23ea8897", + }, + }, + }, + }, + }, + wantErrStr: "v0.0.2: empty protocols list", + }, + { + name: "invalid protocol", + input: Metadata{ + Versions: []Version{ + { + Version: "0.0.2", + Protocols: []string{"5.0", "foo"}, + SHASumsURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS", + SHASumsSignatureURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS.sig", + Targets: []Target{ + { + OS: "darwin", + Arch: "amd64", + Filename: "terraform-provider-metal_0.0.2_darwin_amd64.zip", + DownloadURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_darwin_amd64.zip", + SHASum: "0192019c49306991cf2413d937315a5a5aa83e8a3e01ec0891d3a8460422683a", + }, + { + OS: "darwin", + Arch: "arm64", + Filename: "terraform-provider-metal_0.0.2_darwin_arm64.zip", + DownloadURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_darwin_arm64.zip", + SHASum: "2da0c86252ff399567852412a8b750ca7ab0ffa48bd8e335edc0e8cec4e12195", + }, + { + OS: "linux", + Arch: "arm64", + Filename: "terraform-provider-metal_0.0.2_linux_arm64.zip", + DownloadURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_linux_arm64.zip", + SHASum: "6c17811307361f07919514efc0b2eb7637a9c9f580d7d325871002ae23ea8897", + }, + }, + }, + }, + }, + wantErrStr: "v0.0.2: unsupported protocol found: foo", + }, + { + name: "no targets data", + input: Metadata{ + Versions: []Version{ + { + Version: "0.0.2", + Protocols: []string{"5.0"}, + SHASumsURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS", + SHASumsSignatureURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS.sig", + }, + }, + }, + wantErrStr: "v0.0.2: empty targets list", + }, + { + name: "invalid target os", + input: Metadata{ + Versions: []Version{ + { + Version: "0.0.2", + Protocols: []string{"5.0"}, + SHASumsURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS", + SHASumsSignatureURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS.sig", + Targets: []Target{ + { + OS: "foo", + Arch: "amd64", + Filename: "terraform-provider-metal_0.0.2_darwin_amd64.zip", + DownloadURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_darwin_amd64.zip", + SHASum: "0192019c49306991cf2413d937315a5a5aa83e8a3e01ec0891d3a8460422683a", + }, + { + OS: "darwin", + Arch: "arm64", + Filename: "terraform-provider-metal_0.0.2_darwin_arm64.zip", + DownloadURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_darwin_arm64.zip", + SHASum: "2da0c86252ff399567852412a8b750ca7ab0ffa48bd8e335edc0e8cec4e12195", + }, + { + OS: "linux", + Arch: "arm64", + Filename: "terraform-provider-metal_0.0.2_linux_arm64.zip", + DownloadURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_linux_arm64.zip", + SHASum: "6c17811307361f07919514efc0b2eb7637a9c9f580d7d325871002ae23ea8897", + }, + }, + }, + }, + }, + wantErrStr: "v0.0.2: target foo-amd64: unsupported OS: foo", + }, + { + name: "invalid target arch", + input: Metadata{ + Versions: []Version{ + { + Version: "0.0.2", + Protocols: []string{"5.0"}, + SHASumsURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS", + SHASumsSignatureURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS.sig", + Targets: []Target{ + { + OS: "darwin", + Arch: "foo", + Filename: "terraform-provider-metal_0.0.2_darwin_amd64.zip", + DownloadURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_darwin_amd64.zip", + SHASum: "0192019c49306991cf2413d937315a5a5aa83e8a3e01ec0891d3a8460422683a", + }, + { + OS: "darwin", + Arch: "arm64", + Filename: "terraform-provider-metal_0.0.2_darwin_arm64.zip", + DownloadURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_darwin_arm64.zip", + SHASum: "2da0c86252ff399567852412a8b750ca7ab0ffa48bd8e335edc0e8cec4e12195", + }, + { + OS: "linux", + Arch: "arm64", + Filename: "terraform-provider-metal_0.0.2_linux_arm64.zip", + DownloadURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_linux_arm64.zip", + SHASum: "6c17811307361f07919514efc0b2eb7637a9c9f580d7d325871002ae23ea8897", + }, + }, + }, + }, + }, + wantErrStr: "v0.0.2: target darwin-foo: unsupported ARCH: foo", + }, + { + name: "filename does not match url", + input: Metadata{ + Versions: []Version{ + { + Version: "0.0.2", + Protocols: []string{"5.0"}, + SHASumsURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS", + SHASumsSignatureURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS.sig", + Targets: []Target{ + { + OS: "darwin", + Arch: "amd64", + Filename: "foobar.zip", + DownloadURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_darwin_amd64.zip", + SHASum: "0192019c49306991cf2413d937315a5a5aa83e8a3e01ec0891d3a8460422683a", + }, + { + OS: "darwin", + Arch: "arm64", + Filename: "terraform-provider-metal_0.0.2_darwin_arm64.zip", + DownloadURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_darwin_arm64.zip", + SHASum: "2da0c86252ff399567852412a8b750ca7ab0ffa48bd8e335edc0e8cec4e12195", + }, + { + OS: "linux", + Arch: "arm64", + Filename: "terraform-provider-metal_0.0.2_linux_arm64.zip", + DownloadURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_linux_arm64.zip", + SHASum: "6c17811307361f07919514efc0b2eb7637a9c9f580d7d325871002ae23ea8897", + }, + }, + }, + }, + }, + wantErrStr: "v0.0.2: target darwin-amd64: 'filename' is not consistent with 'download_url'", + }, + { + name: "target shasum length is wrong", + input: Metadata{ + Versions: []Version{ + { + Version: "0.0.2", + Protocols: []string{"5.0"}, + SHASumsURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS", + SHASumsSignatureURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS.sig", + Targets: []Target{ + { + OS: "darwin", + Arch: "amd64", + Filename: "terraform-provider-metal_0.0.2_darwin_amd64.zip", + DownloadURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_darwin_amd64.zip", + SHASum: "0192019c49306991cf2413d937315a5a5aa83e8a3e01ec0891d3a8460422683a", + }, + { + OS: "darwin", + Arch: "arm64", + Filename: "terraform-provider-metal_0.0.2_darwin_arm64.zip", + DownloadURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_darwin_arm64.zip", + SHASum: "2da0c86252ff399567852412a8b750ca7ab0ffa48bd8e335edc0e8cec4e12195", + }, + { + OS: "linux", + Arch: "arm64", + Filename: "terraform-provider-metal_0.0.2_linux_arm64.zip", + DownloadURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_linux_arm64.zip", + SHASum: "6c17811307361f07919514efc0b2eb7637a9c9f580d7d325871002ae23ea889", + }, + }, + }, + }, + }, + wantErrStr: "v0.0.2: target linux-arm64: SHASum length is wrong", + }, + { + name: "multiple errors", + input: Metadata{ + Versions: []Version{ + { + Version: "0.0.1", + Protocols: []string{"5.0", "xxx"}, + SHASumsURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.1/terraform-provider-metal_0.0.1_SHA256SUMS", + SHASumsSignatureURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.1/terraform-provider-metal_0.0.1_SHA256SUMS.sig", + }, + { + Version: "0.0.2", + Protocols: []string{"5.0"}, + SHASumsURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS", + SHASumsSignatureURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_SHA256SUMS.sig", + Targets: []Target{ + { + OS: "foo", + Arch: "amd64", + Filename: "terraform-provider-metal_0.0.2_darwin_amd64.zip", + DownloadURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_darwin_amd64.zip", + SHASum: "0192019c49306991cf2413d937315a5a5aa83e8a3e01ec0891d3a8460422683a", + }, + { + OS: "darwin", + Arch: "arm64", + Filename: "terraform-provider-metal_0.0.2_darwin_arm64.zip", + DownloadURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_darwin_arm64.zip", + SHASum: "2da0c86252ff399567852412a8b750ca7ab0ffa48bd8e335edc0e8cec4e12195", + }, + { + OS: "linux", + Arch: "arm64", + Filename: "terraform-provider-metal_0.0.2_linux_arm64.zip", + DownloadURL: "https://github.com/metal-stack-cloud/terraform-provider-metal/releases/download/v0.0.2/terraform-provider-metal_0.0.2_linux_arm64.zip", + SHASum: "6c17811307361f07919514efc0b2eb7637a9c9f580d7d325871002ae23ea889", + }, + }, + }, + }, + }, + wantErrStr: `v0.0.1: unsupported protocol found: xxx +v0.0.1: empty targets list +v0.0.2: target foo-amd64: unsupported OS: foo +v0.0.2: target linux-arm64: SHASum length is wrong`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := Validate(tt.input) + switch tt.wantErrStr != "" { + case true: + if err == nil || tt.wantErrStr != err.Error() { + t.Fatalf("unexpected error message, want = %s, got = %v", tt.wantErrStr, err) + } + default: + if err != nil { + t.Fatalf("unexpected error message: %v", err) + } + } + }) + } +} diff --git a/src/internal/validate/errors.go b/src/internal/validate/errors.go new file mode 100644 index 0000000000..ce3b090f7d --- /dev/null +++ b/src/internal/validate/errors.go @@ -0,0 +1,5 @@ +package validate + +import "errors" + +var ErrorEmptyList = errors.New("found empty list of versions") diff --git a/src/internal/validate/validate-version.go b/src/internal/validate/validate-version.go new file mode 100644 index 0000000000..b9056c8ffc --- /dev/null +++ b/src/internal/validate/validate-version.go @@ -0,0 +1,11 @@ +package validate + +import "golang.org/x/mod/semver" + +// IsValidVersion check if the version is semver compatible. +func IsValidVersion(ver string) bool { + if ver[0] != 'v' { + ver = "v" + ver + } + return semver.IsValid(ver) +} From bc2290e308612c640e6e7cb8be8f9464452e3e10 Mon Sep 17 00:00:00 2001 From: Dmitry Kisler Date: Tue, 30 Jan 2024 16:53:37 +0100 Subject: [PATCH 05/13] fix: revert bulk renaming spil Signed-off-by: Dmitry Kisler --- src/internal/provider/build.go | 2 +- src/internal/provider/update.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/internal/provider/build.go b/src/internal/provider/build.go index 350070ba35..ef99a3caec 100644 --- a/src/internal/provider/build.go +++ b/src/internal/provider/build.go @@ -31,7 +31,7 @@ func (meta Metadata) filterNewReleases(releases []string) []string { return newReleases } -// getSemverTags returns a list of validate.go tags for the module fetched from GitHub. +// getSemverTags returns a list of semver tags for the module fetched from GitHub. func (p Provider) getSemverTags() ([]string, error) { tags, err := p.Github.GetTags(p.RepositoryURL()) if err != nil { diff --git a/src/internal/provider/update.go b/src/internal/provider/update.go index 358ba6db92..5b30ef75c6 100644 --- a/src/internal/provider/update.go +++ b/src/internal/provider/update.go @@ -52,8 +52,8 @@ func (p Provider) shouldUpdateMetadataFile() (bool, error) { return true, nil } -// getSemVerTagsFromRSS returns a list of validate.go tags from the RSS feed -// ignoring all non-valid validate.go tags +// getSemVerTagsFromRSS returns a list of semver tags from the RSS feed +// ignoring all non-valid semver tags func (p Provider) getSemVerTagsFromRSS() ([]string, error) { releasesRssUrl := p.RSSURL() tags, err := p.Github.GetTagsFromRSS(releasesRssUrl) @@ -71,7 +71,7 @@ func (p Provider) getSemVerTagsFromRSS() ([]string, error) { return semverTags, nil } -// getLastSemVerTag returns the most recently created validate.go tag from the RSS feed +// getLastSemVerTag returns the most recently created semver tag from the RSS feed // by sorting the tags by descending creation date func (p Provider) getLastSemVerTag() (string, error) { semverTags, err := p.getSemVerTagsFromRSS() From 50788696b9ec6f935ee9fdb187d43f3705a73216 Mon Sep 17 00:00:00 2001 From: Dmitry Kisler Date: Tue, 30 Jan 2024 22:42:06 +0100 Subject: [PATCH 06/13] fix modules validation ci Signed-off-by: Dmitry Kisler --- .github/workflows/validate-json-module.yml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/validate-json-module.yml b/.github/workflows/validate-json-module.yml index d995f901a6..1f6c877b4b 100644 --- a/.github/workflows/validate-json-module.yml +++ b/.github/workflows/validate-json-module.yml @@ -15,14 +15,25 @@ jobs: steps: - uses: actions/checkout@v4 + with: + fetch-depth: 2 + - uses: actions/setup-go@v4 with: go-version-file: './src/go.mod' + - name: List updated filed + id: updated + uses: tj-actions/changed-files@v42 + with: + files: modules/**/*.json + - name: Validate JSON working-directory: ./src + env: + CHANGED_FILES: ${{ steps.updated.outputs.all_changed_files }} run: | - for path in $(git diff HEAD~1 HEAD --name-only) + for path in "$CHANGED_FILES" do - go run ./cmd/validate/main.go module $path + go run ./cmd/validate/main.go module "$path" done From 9a1c1edfc468dfb6e78a5c673675bd323df6bc0b Mon Sep 17 00:00:00 2001 From: Dmitry Kisler Date: Tue, 30 Jan 2024 23:28:41 +0100 Subject: [PATCH 07/13] add formatted logger Signed-off-by: Dmitry Kisler --- src/cmd/validate/main.go | 28 ++++++++++++++++++-------- src/internal/module/validate.go | 14 +++++++------ src/internal/module/validate_test.go | 26 +++++++++++++++--------- src/internal/provider/validate.go | 11 ++++++---- src/internal/provider/validate_test.go | 21 ++++++++++--------- src/internal/validate/errors.go | 16 ++++++++++++++- 6 files changed, 78 insertions(+), 38 deletions(-) diff --git a/src/cmd/validate/main.go b/src/cmd/validate/main.go index d0364a3862..b57e24f491 100644 --- a/src/cmd/validate/main.go +++ b/src/cmd/validate/main.go @@ -8,6 +8,7 @@ import ( "github.com/opentofu/registry-stable/internal/module" "github.com/opentofu/registry-stable/internal/provider" + "github.com/opentofu/registry-stable/internal/validate" ) func main() { @@ -26,7 +27,7 @@ CMD: args := os.Args[1:] if len(args) < 1 { - logger.Error(errorCLI, slog.String("error", cliError)) + logger.Error(cliError, slog.String("type", errorCLI)) os.Exit(1) } @@ -37,7 +38,7 @@ CMD: if len(args) != 2 { fmt.Print(helpStr) - logger.Error(errorCLI, slog.String("error", cliError)) + logger.Error(cliError, slog.String("type", errorCLI)) os.Exit(1) } @@ -66,13 +67,24 @@ CMD: default: fmt.Print(helpStr) - logger.Error(errorCLI, slog.String("error", fmt.Sprintf("%s command is not supported", cmd))) + logger.Error(fmt.Sprintf("%s command is not supported", cmd), slog.String("type", errorCLI)) os.Exit(1) } if err != nil { - logger.Error(errType) - fmt.Println(err) + args := []any{ + slog.String("type", errType), + slog.String("path", path), + } + switch err.(type) { + case validate.Errors: + for _, e := range err.(validate.Errors) { + logger.Error(e.Error(), args...) + } + default: + logger.Error(err.Error(), args...) + } + os.Exit(1) } } @@ -88,7 +100,7 @@ func readJSONFile(p string, v any) error { } const ( - errorValidation = "validation error" - errorCLI = "CLI error" - errorJSONParsing = "JSON parsing error" + errorValidation = "validation" + errorJSONParsing = "parsing" + errorCLI = "CLI" ) diff --git a/src/internal/module/validate.go b/src/internal/module/validate.go index b72e1c2fd0..3455e23c01 100644 --- a/src/internal/module/validate.go +++ b/src/internal/module/validate.go @@ -1,7 +1,6 @@ package module import ( - "errors" "fmt" "github.com/opentofu/registry-stable/internal/validate" @@ -9,17 +8,20 @@ import ( // Validate validates module's metadata. func Validate(v Metadata) error { + var errs = make([]error, 0) if len(v.Versions) < 1 { - return validate.ErrorEmptyList + errs = append(errs, validate.ErrorEmptyList) } - var errs = make([]error, 0, len(v.Versions)) for _, ver := range v.Versions { if !validate.IsValidVersion(ver.Version) { - err := fmt.Errorf("found semver-incompatible version: %s", ver.Version) - errs = append(errs, err) + errs = append(errs, fmt.Errorf("found semver-incompatible version: %s", ver.Version)) } } - return errors.Join(errs...) + if len(errs) == 0 { + return nil + } + + return validate.Errors(errs) } diff --git a/src/internal/module/validate_test.go b/src/internal/module/validate_test.go index a0026cd795..620e98eef4 100644 --- a/src/internal/module/validate_test.go +++ b/src/internal/module/validate_test.go @@ -6,9 +6,9 @@ import ( func TestValidate(t *testing.T) { type TestCase struct { - name string - input Metadata - wantErr bool + name string + input Metadata + wantErrStr string } tests := []TestCase{ @@ -23,19 +23,27 @@ func TestValidate(t *testing.T) { input: Metadata{ Versions: []Version{{"0.0.2"}, {"foo"}}, }, - wantErr: true, + wantErrStr: "found semver-incompatible version: foo\n", }, { - name: "empty-versions-list", - input: Metadata{}, - wantErr: true, + name: "empty-versions-list", + input: Metadata{}, + wantErrStr: "found empty list of versions\n", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := Validate(tt.input); (err != nil) != tt.wantErr { - t.Fatalf("Validate(%v) unexpected error = %v", tt.input, err) + err := Validate(tt.input) + switch tt.wantErrStr != "" { + case true: + if err == nil || tt.wantErrStr != err.Error() { + t.Fatalf("unexpected error message, want = %s, got = %v", tt.wantErrStr, err) + } + default: + if err != nil { + t.Fatalf("unexpected error message: %v", err) + } } }) } diff --git a/src/internal/provider/validate.go b/src/internal/provider/validate.go index 37d1b53864..ba6e6b4bef 100644 --- a/src/internal/provider/validate.go +++ b/src/internal/provider/validate.go @@ -1,7 +1,6 @@ package provider import ( - "errors" "fmt" "slices" "strings" @@ -11,18 +10,22 @@ import ( // Validate validates provider's metadata. func Validate(v Metadata) error { + var errs = make([]error, 0) if len(v.Versions) < 1 { - return validate.ErrorEmptyList + errs = append(errs, validate.ErrorEmptyList) } - var errs = make([]error, 0, len(v.Versions)) for _, ver := range v.Versions { for _, err := range validateProviderVersion(ver) { errs = append(errs, fmt.Errorf("v%s: %w", ver.Version, err)) } } - return errors.Join(errs...) + if len(errs) == 0 { + return nil + } + + return validate.Errors(errs) } func validateProviderVersion(ver Version) []error { diff --git a/src/internal/provider/validate_test.go b/src/internal/provider/validate_test.go index 8afabdf91c..607d8d9d51 100644 --- a/src/internal/provider/validate_test.go +++ b/src/internal/provider/validate_test.go @@ -51,7 +51,7 @@ func TestValidate(t *testing.T) { { name: "no versions data", input: Metadata{}, - wantErrStr: "found empty list of versions", + wantErrStr: "found empty list of versions\n", }, { name: "invalid version", @@ -103,7 +103,7 @@ func TestValidate(t *testing.T) { }, }, }, - wantErrStr: "vfoo: found semver-incompatible version: foo", + wantErrStr: "vfoo: found semver-incompatible version: foo\n", }, { name: "no protocols", @@ -139,7 +139,7 @@ func TestValidate(t *testing.T) { }, }, }, - wantErrStr: "v0.0.2: empty protocols list", + wantErrStr: "v0.0.2: empty protocols list\n", }, { name: "invalid protocol", @@ -176,7 +176,7 @@ func TestValidate(t *testing.T) { }, }, }, - wantErrStr: "v0.0.2: unsupported protocol found: foo", + wantErrStr: "v0.0.2: unsupported protocol found: foo\n", }, { name: "no targets data", @@ -190,7 +190,7 @@ func TestValidate(t *testing.T) { }, }, }, - wantErrStr: "v0.0.2: empty targets list", + wantErrStr: "v0.0.2: empty targets list\n", }, { name: "invalid target os", @@ -227,7 +227,7 @@ func TestValidate(t *testing.T) { }, }, }, - wantErrStr: "v0.0.2: target foo-amd64: unsupported OS: foo", + wantErrStr: "v0.0.2: target foo-amd64: unsupported OS: foo\n", }, { name: "invalid target arch", @@ -264,7 +264,7 @@ func TestValidate(t *testing.T) { }, }, }, - wantErrStr: "v0.0.2: target darwin-foo: unsupported ARCH: foo", + wantErrStr: "v0.0.2: target darwin-foo: unsupported ARCH: foo\n", }, { name: "filename does not match url", @@ -301,7 +301,7 @@ func TestValidate(t *testing.T) { }, }, }, - wantErrStr: "v0.0.2: target darwin-amd64: 'filename' is not consistent with 'download_url'", + wantErrStr: "v0.0.2: target darwin-amd64: 'filename' is not consistent with 'download_url'\n", }, { name: "target shasum length is wrong", @@ -338,7 +338,7 @@ func TestValidate(t *testing.T) { }, }, }, - wantErrStr: "v0.0.2: target linux-arm64: SHASum length is wrong", + wantErrStr: "v0.0.2: target linux-arm64: SHASum length is wrong\n", }, { name: "multiple errors", @@ -384,7 +384,8 @@ func TestValidate(t *testing.T) { wantErrStr: `v0.0.1: unsupported protocol found: xxx v0.0.1: empty targets list v0.0.2: target foo-amd64: unsupported OS: foo -v0.0.2: target linux-arm64: SHASum length is wrong`, +v0.0.2: target linux-arm64: SHASum length is wrong +`, }, } diff --git a/src/internal/validate/errors.go b/src/internal/validate/errors.go index ce3b090f7d..e0ae8e0710 100644 --- a/src/internal/validate/errors.go +++ b/src/internal/validate/errors.go @@ -1,5 +1,19 @@ package validate -import "errors" +import ( + "errors" + "strings" +) var ErrorEmptyList = errors.New("found empty list of versions") + +type Errors []error + +func (e Errors) Error() string { + var buf strings.Builder + for _, el := range e { + _, _ = buf.WriteString(el.Error()) + _, _ = buf.WriteString("\n") + } + return buf.String() +} From a1db6b69b197a222a3f219e40b175162c6198340 Mon Sep 17 00:00:00 2001 From: Dmitry Kisler Date: Tue, 30 Jan 2024 23:29:33 +0100 Subject: [PATCH 08/13] update ci for module Signed-off-by: Dmitry Kisler --- .github/workflows/validate-json-module.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/validate-json-module.yml b/.github/workflows/validate-json-module.yml index 1f6c877b4b..50a701a02b 100644 --- a/.github/workflows/validate-json-module.yml +++ b/.github/workflows/validate-json-module.yml @@ -22,6 +22,13 @@ jobs: with: go-version-file: './src/go.mod' + - name: Build validator + working-directory: ./src + run: | + mkdir -p /tmp/validate + go build -o /tmp/validate/run ./cmd/validate/main.go + chmod +x /tmp/validate/run + - name: List updated filed id: updated uses: tj-actions/changed-files@v42 @@ -29,11 +36,10 @@ jobs: files: modules/**/*.json - name: Validate JSON - working-directory: ./src env: CHANGED_FILES: ${{ steps.updated.outputs.all_changed_files }} run: | for path in "$CHANGED_FILES" do - go run ./cmd/validate/main.go module "$path" + /tmp/validate/run module "$path" done From f8f34fb8f578daa78635147feb5624bf85f6f62e Mon Sep 17 00:00:00 2001 From: Dmitry Kisler Date: Tue, 30 Jan 2024 23:33:45 +0100 Subject: [PATCH 09/13] add provider's JSON validation CI pipeline definition Signed-off-by: Dmitry Kisler --- .github/workflows/validate-json-provider.yml | 23 +++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/.github/workflows/validate-json-provider.yml b/.github/workflows/validate-json-provider.yml index b1188baab9..0a6a633fee 100644 --- a/.github/workflows/validate-json-provider.yml +++ b/.github/workflows/validate-json-provider.yml @@ -15,14 +15,31 @@ jobs: steps: - uses: actions/checkout@v4 + with: + fetch-depth: 2 + - uses: actions/setup-go@v4 with: go-version-file: './src/go.mod' - - name: Validate JSON + - name: Build validator working-directory: ./src run: | - for path in $(git diff HEAD~1 HEAD --name-only) + mkdir -p /tmp/validate + go build -o /tmp/validate/run ./cmd/validate/main.go + chmod +x /tmp/validate/run + + - name: List updated filed + id: updated + uses: tj-actions/changed-files@v42 + with: + files: providers/**/*.json + + - name: Validate JSON + env: + CHANGED_FILES: ${{ steps.updated.outputs.all_changed_files }} + run: | + for path in "$CHANGED_FILES" do - go run ./cmd/validate/main.go provider $path + /tmp/validate/run provider "$path" done From 185a6fab38f04f9239e95f5288184039fe01e855 Mon Sep 17 00:00:00 2001 From: Dmitry Kisler Date: Tue, 30 Jan 2024 23:35:36 +0100 Subject: [PATCH 10/13] chore: styling Signed-off-by: Dmitry Kisler --- src/internal/provider/validate.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/internal/provider/validate.go b/src/internal/provider/validate.go index ba6e6b4bef..0b541e8594 100644 --- a/src/internal/provider/validate.go +++ b/src/internal/provider/validate.go @@ -11,6 +11,7 @@ import ( // Validate validates provider's metadata. func Validate(v Metadata) error { var errs = make([]error, 0) + if len(v.Versions) < 1 { errs = append(errs, validate.ErrorEmptyList) } From 0725ab9acecbf065f27b47bd53b01ba45fd23801 Mon Sep 17 00:00:00 2001 From: Dmitry Kisler Date: Tue, 30 Jan 2024 23:38:55 +0100 Subject: [PATCH 11/13] chore: fixes the CI job name Signed-off-by: Dmitry Kisler --- .github/workflows/validate-json-provider.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validate-json-provider.yml b/.github/workflows/validate-json-provider.yml index 0a6a633fee..4b4fe2161d 100644 --- a/.github/workflows/validate-json-provider.yml +++ b/.github/workflows/validate-json-provider.yml @@ -8,7 +8,7 @@ on: - 'provider-**' jobs: - validate-metadata-module: + validate-metadata-provider: runs-on: ubuntu-latest environment: name: ${{ inputs.environment }} From a245bfccab5c2851360d48bceeed44564c640d84 Mon Sep 17 00:00:00 2001 From: Dmitry Kisler Date: Wed, 31 Jan 2024 14:41:13 +0100 Subject: [PATCH 12/13] fix typo in CI step Co-authored-by: James Humphries Signed-off-by: Dmitry Kisler --- .github/workflows/validate-json-provider.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validate-json-provider.yml b/.github/workflows/validate-json-provider.yml index 4b4fe2161d..42f09af27a 100644 --- a/.github/workflows/validate-json-provider.yml +++ b/.github/workflows/validate-json-provider.yml @@ -29,7 +29,7 @@ jobs: go build -o /tmp/validate/run ./cmd/validate/main.go chmod +x /tmp/validate/run - - name: List updated filed + - name: List updated files id: updated uses: tj-actions/changed-files@v42 with: From dd9a483a95193134ef65394973e5a3c3352a08da Mon Sep 17 00:00:00 2001 From: Dmitry Kisler Date: Wed, 31 Jan 2024 14:41:22 +0100 Subject: [PATCH 13/13] fix typo in CI step Co-authored-by: James Humphries Signed-off-by: Dmitry Kisler --- .github/workflows/validate-json-module.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validate-json-module.yml b/.github/workflows/validate-json-module.yml index 50a701a02b..a64401c6ae 100644 --- a/.github/workflows/validate-json-module.yml +++ b/.github/workflows/validate-json-module.yml @@ -29,7 +29,7 @@ jobs: go build -o /tmp/validate/run ./cmd/validate/main.go chmod +x /tmp/validate/run - - name: List updated filed + - name: List updated files id: updated uses: tj-actions/changed-files@v42 with: