diff --git a/.github/workflows/validate-json-module.yml b/.github/workflows/validate-json-module.yml new file mode 100644 index 0000000000..a64401c6ae --- /dev/null +++ b/.github/workflows/validate-json-module.yml @@ -0,0 +1,45 @@ +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 + with: + fetch-depth: 2 + + - uses: actions/setup-go@v4 + 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 files + id: updated + uses: tj-actions/changed-files@v42 + with: + files: modules/**/*.json + + - name: Validate JSON + env: + CHANGED_FILES: ${{ steps.updated.outputs.all_changed_files }} + run: | + for path in "$CHANGED_FILES" + do + /tmp/validate/run module "$path" + done diff --git a/.github/workflows/validate-json-provider.yml b/.github/workflows/validate-json-provider.yml new file mode 100644 index 0000000000..42f09af27a --- /dev/null +++ b/.github/workflows/validate-json-provider.yml @@ -0,0 +1,45 @@ +name: Validate Provider JSON + +on: + push: + paths: + - 'providers/**/*.json' + branches: + - 'provider-**' + +jobs: + validate-metadata-provider: + runs-on: ubuntu-latest + environment: + name: ${{ inputs.environment }} + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - uses: actions/setup-go@v4 + 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 files + 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 + /tmp/validate/run provider "$path" + done diff --git a/src/cmd/validate/main.go b/src/cmd/validate/main.go new file mode 100644 index 0000000000..b57e24f491 --- /dev/null +++ b/src/cmd/validate/main.go @@ -0,0 +1,106 @@ +package main + +import ( + "encoding/json" + "fmt" + "log/slog" + "os" + + "github.com/opentofu/registry-stable/internal/module" + "github.com/opentofu/registry-stable/internal/provider" + "github.com/opentofu/registry-stable/internal/validate" +) + +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, slog.String("type", errorCLI)) + os.Exit(1) + } + + if args[0] == "help" { + fmt.Print(helpStr) + os.Exit(0) + } + + if len(args) != 2 { + fmt.Print(helpStr) + logger.Error(cliError, slog.String("type", errorCLI)) + os.Exit(1) + } + + path := args[1] + + var ( + err error + errType string = errorJSONParsing + ) + switch cmd := args[0]; cmd { + case "module": + var v module.Metadata + err = readJSONFile(path, &v) + if err == nil { + errType = errorValidation + err = module.Validate(v) + } + + case "provider": + var v provider.Metadata + err = readJSONFile(path, &v) + if err == nil { + errType = errorValidation + err = provider.Validate(v) + } + + default: + fmt.Print(helpStr) + logger.Error(fmt.Sprintf("%s command is not supported", cmd), slog.String("type", errorCLI)) + os.Exit(1) + } + + if err != nil { + 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) + } +} + +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) +} + +const ( + errorValidation = "validation" + errorJSONParsing = "parsing" + errorCLI = "CLI" +) diff --git a/src/internal/module/validate.go b/src/internal/module/validate.go new file mode 100644 index 0000000000..3455e23c01 --- /dev/null +++ b/src/internal/module/validate.go @@ -0,0 +1,27 @@ +package module + +import ( + "fmt" + + "github.com/opentofu/registry-stable/internal/validate" +) + +// Validate validates module's metadata. +func Validate(v Metadata) error { + var errs = make([]error, 0) + if len(v.Versions) < 1 { + errs = append(errs, validate.ErrorEmptyList) + } + + for _, ver := range v.Versions { + if !validate.IsValidVersion(ver.Version) { + errs = append(errs, fmt.Errorf("found semver-incompatible version: %s", ver.Version)) + } + } + + 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 new file mode 100644 index 0000000000..620e98eef4 --- /dev/null +++ b/src/internal/module/validate_test.go @@ -0,0 +1,50 @@ +package module + +import ( + "testing" +) + +func TestValidate(t *testing.T) { + type TestCase struct { + name string + input Metadata + wantErrStr string + } + + 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"}}, + }, + wantErrStr: "found semver-incompatible version: foo\n", + }, + { + 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) { + 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 new file mode 100644 index 0000000000..0b541e8594 --- /dev/null +++ b/src/internal/provider/validate.go @@ -0,0 +1,101 @@ +package provider + +import ( + "fmt" + "slices" + "strings" + + "github.com/opentofu/registry-stable/internal/validate" +) + +// 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) + } + + for _, ver := range v.Versions { + for _, err := range validateProviderVersion(ver) { + errs = append(errs, fmt.Errorf("v%s: %w", ver.Version, err)) + } + } + + if len(errs) == 0 { + return nil + } + + return validate.Errors(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..607d8d9d51 --- /dev/null +++ b/src/internal/provider/validate_test.go @@ -0,0 +1,407 @@ +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\n", + }, + { + 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\n", + }, + { + 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\n", + }, + { + 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\n", + }, + { + 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\n", + }, + { + 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\n", + }, + { + 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\n", + }, + { + 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'\n", + }, + { + 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\n", + }, + { + 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..e0ae8e0710 --- /dev/null +++ b/src/internal/validate/errors.go @@ -0,0 +1,19 @@ +package validate + +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() +} 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) +}