Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Schema and strict validation radixconfig #95

Merged
merged 5 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 95 additions & 13 deletions cmd/validateRadixConfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ import (
"github.com/equinor/radix-cli/pkg/flagnames"
radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1"
"github.com/equinor/radix-operator/pkg/apis/radixvalidators"
"github.com/equinor/radix-operator/pkg/apis/utils"
"github.com/pkg/errors"
"github.com/santhosh-tekuri/jsonschema/v5"
_ "github.com/santhosh-tekuri/jsonschema/v5/httploader"
"github.com/spf13/cobra"
"sigs.k8s.io/yaml"
)
Expand All @@ -33,46 +34,126 @@ var validateRadixConfigCmd = &cobra.Command{
Use: "radix-config",
Short: "Validate radixconfig.yaml",
Long: `Check radixconfig.yaml for structural and logical errors`,
RunE: func(cmd *cobra.Command, args []string) error {

cmd.SilenceUsage = true

Run: func(cmd *cobra.Command, args []string) {
radixconfig, err := cmd.Flags().GetString(flagnames.ConfigFile)
if err != nil {
return err
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
}

printfile, err := cmd.Flags().GetBool(flagnames.Print)
if err != nil {
return err
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
}

schema, err := cmd.Flags().GetString(flagnames.Schema)
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
}

fmt.Fprintf(os.Stderr, "Validating %s\n", radixconfig)
if _, err := os.Stat(radixconfig); errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("RadixConfig file not found: %s", radixconfig)
fmt.Fprintln(os.Stderr, "RadixConfig file not found")
os.Exit(1)
}

ra, err := utils.GetRadixApplicationFromFile(radixconfig)
raw, err := os.ReadFile(radixconfig)
if err != nil {
return fmt.Errorf("RadixConfig is invalid: %w", err)
fmt.Fprintf(os.Stderr, "failed to read file: %v\n", err)
os.Exit(1)
}

ra, err := unmarshalRadixApplication(raw)
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
}

if printfile {
err = printRA(ra)
if err != nil {
return err
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
}
}

validationErrors, err := validateSchema(raw, schema)
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
}

err = radixvalidators.IsRadixApplicationValid(ra)
if err != nil {
return fmt.Errorf("RadixConfig is invalid:\n%w", err)
validationErrors = append(validationErrors, err)
}

err = strictUnmarshalValidation(raw)
if err != nil {
validationErrors = append(validationErrors, err)
}

if len(validationErrors) > 0 {
for _, err := range validationErrors {
fmt.Fprintf(os.Stderr, " - %s\n", err)
}

fmt.Fprintln(os.Stderr, "RadixConfig is invalid")
os.Exit(2)
}

fmt.Fprintln(os.Stderr, "RadixConfig is valid")
return nil
},
}

func validateSchema(raw []byte, schema string) (validationErrors []error, err error) {
s, err := jsonschema.Compile(schema)
if err != nil {
return nil, fmt.Errorf("failed compiling schema %s: %s", schema, err)
}

var obj interface{}
err = yaml.Unmarshal(raw, &obj)
if err != nil {
return nil, fmt.Errorf("failed to parse file: %v", err)
}

err = s.Validate(obj)
var verr *jsonschema.ValidationError
if errors.As(err, &verr) {
for _, err := range verr.Causes {
validationErrors = append(validationErrors, fmt.Errorf("%s: %s", err.InstanceLocation, err.Message))
}
} else {
return nil, err
}

return validationErrors, nil
}

func strictUnmarshalValidation(raw []byte) error {
radixApp := &radixv1.RadixApplication{}

err := yaml.UnmarshalStrict(raw, radixApp)
if err != nil {
return fmt.Errorf("strict test failed: %v", err)
}

return nil
}
func unmarshalRadixApplication(raw []byte) (*radixv1.RadixApplication, error) {
radixApp := &radixv1.RadixApplication{}

err := yaml.Unmarshal(raw, radixApp)
if err != nil {
return nil, fmt.Errorf("strict test failed: %v", err)
}

return radixApp, nil
}

func printRA(ra *radixv1.RadixApplication) error {
b, err := yaml.Marshal(ra)
if err != nil {
Expand All @@ -87,6 +168,7 @@ func init() {
validateCmd.AddCommand(validateRadixConfigCmd)
validateRadixConfigCmd.Flags().StringP(flagnames.ConfigFile, "f", "radixconfig.yaml", "Name of the radixconfig file. Defaults to radixconfig.yaml in current directory")
validateRadixConfigCmd.Flags().BoolP(flagnames.Print, "p", false, "Print parsed config file")
validateRadixConfigCmd.Flags().StringP(flagnames.Schema, "s", "https://raw.githubusercontent.com/equinor/radix-operator/release/json-schema/radixapplication.json", "Validate against schema")
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need a shorthand for schema? Most users will never use it, and the more shorthands we add, the more chance of a conflict with other flags later.
Also, add some info about what values this flag accepts: url (https://) or file (file://)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

enig! la inn Validate against schema. http://, file:// or path is supported, og installerte samme versjon som Radix-API, og på samme måte :)


// Allow but hide token-env flag so radix-github-actions won't interfere
validateRadixConfigCmd.Flags().Bool(flagnames.TokenEnvironment, false, fmt.Sprintf("Take the token from environment variable %s", client.TokenEnvironmentName))
Expand Down
71 changes: 40 additions & 31 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
module github.com/equinor/radix-cli

go 1.21
go 1.22.0

toolchain go1.21.0
toolchain go1.22.1

require (
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1
github.com/equinor/radix-operator v1.51.2
github.com/fatih/color v1.15.0
github.com/equinor/radix-operator v1.55.2
github.com/fatih/color v1.16.0
github.com/go-openapi/errors v0.20.4
github.com/go-openapi/runtime v0.26.2
github.com/go-openapi/strfmt v0.21.8
github.com/go-openapi/swag v0.22.7
github.com/go-openapi/validate v0.22.3
github.com/pkg/errors v0.9.1
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.0
k8s.io/utils v0.0.0-20240102154912-e7106e64919e
k8s.io/utils v0.0.0-20240310230437-4693a0247e57
sigs.k8s.io/yaml v1.4.0
)

Expand All @@ -27,8 +28,12 @@ require (
github.com/cert-manager/cert-manager v1.14.2 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/emicklei/go-restful/v3 v3.11.2 // indirect
github.com/equinor/radix-common v1.7.1 // indirect
github.com/evanphx/json-patch v5.8.1+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.9.0 // indirect
github.com/expr-lang/expr v1.15.8 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.21.4 // indirect
Expand All @@ -37,60 +42,64 @@ require (
github.com/go-openapi/loads v0.21.2 // indirect
github.com/go-openapi/spec v0.20.11 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.0.0 // indirect
github.com/golang-jwt/jwt/v5 v5.2.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.5.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kedacore/keda/v2 v2.13.1 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.70.0 // indirect
github.com/prometheus-operator/prometheus-operator/pkg/client v0.70.0 // indirect
github.com/prometheus/client_golang v1.18.0 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/client_golang v1.19.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.53.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/rs/zerolog v1.32.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
go.mongodb.org/mongo-driver v1.13.1 // indirect
go.opentelemetry.io/otel v1.21.0 // indirect
go.opentelemetry.io/otel/metric v1.21.0 // indirect
go.opentelemetry.io/otel/trace v1.21.0 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/oauth2 v0.15.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/term v0.15.0 // indirect
go.opentelemetry.io/otel v1.22.0 // indirect
go.opentelemetry.io/otel/metric v1.22.0 // indirect
go.opentelemetry.io/otel/trace v1.22.0 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/oauth2 v0.19.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/term v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.29.0 // indirect
k8s.io/apiextensions-apiserver v0.29.0 // indirect
k8s.io/apimachinery v0.29.0 // indirect
k8s.io/client-go v0.29.0 // indirect
k8s.io/klog/v2 v2.110.1 // indirect
k8s.io/kube-openapi v0.0.0-20240103051144-eec4567ac022 // indirect
sigs.k8s.io/controller-runtime v0.16.3 // indirect
k8s.io/api v0.30.1 // indirect
k8s.io/apiextensions-apiserver v0.30.1 // indirect
k8s.io/apimachinery v0.30.1 // indirect
k8s.io/client-go v0.30.1 // indirect
k8s.io/klog/v2 v2.120.1 // indirect
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
knative.dev/pkg v0.0.0-20240116073220-b488e7be5902 // indirect
sigs.k8s.io/controller-runtime v0.18.2 // indirect
sigs.k8s.io/gateway-api v1.0.0 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/secrets-store-csi-driver v1.4.0 // indirect
Expand Down
Loading
Loading