diff --git a/README.md b/README.md index 74ec401..1818d35 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,17 @@ kploy auth whoami # show the orgs your token can see kploy auth logout # forget the saved token ``` +## Validating `kploy.yaml` + +If your project has a `kploy.yaml` at its repo root, you can sanity-check it locally before pushing: + +```sh +kploy validate-config # defaults to ./kploy.yaml +kploy validate-config -f my.yaml # different path +``` + +Prints rendered hostnames for production and development on success — plus an example preview env if preview environments are enabled. Validation errors include the field path. Does not require authentication. + ## Common workflows ```sh diff --git a/cmd/root.go b/cmd/root.go index 344560d..ee0c184 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -24,6 +24,7 @@ func Root() *cobra.Command { root.AddCommand(imageCommand()) root.AddCommand(orgCommand()) root.AddCommand(repoCommand()) + root.AddCommand(validateConfigCommand()) root.AddCommand(versionCommand()) return root diff --git a/cmd/validate_config.go b/cmd/validate_config.go new file mode 100644 index 0000000..e6dfe4e --- /dev/null +++ b/cmd/validate_config.go @@ -0,0 +1,102 @@ +package cmd + +import ( + "errors" + "fmt" + "os" + "text/tabwriter" + + "github.com/bitcomplete/kployconfig" + "github.com/spf13/cobra" +) + +func validateConfigCommand() *cobra.Command { + var file string + c := &cobra.Command{ + Use: "validate-config", + Short: "Validate a local kploy.yaml file", + Long: `Parse and validate a kploy.yaml file, then print the hostnames +it would render for production and development — plus, if preview +environments are enabled, an example PR preview env.`, + RunE: func(c *cobra.Command, args []string) error { + data, err := os.ReadFile(file) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("no %s in current directory; use --file to point elsewhere", file) + } + return fmt.Errorf("reading %s: %w", file, err) + } + cfg, err := kployconfig.Load(data) + if err != nil { + return err + } + + envs := []struct { + name string + pr int + }{ + {"prod", 0}, + {"development", 0}, + } + hostnames := make(map[string]string, len(envs)) + for _, env := range envs { + h, err := kployconfig.RenderHostname(cfg, env.name, env.pr) + if err != nil { + return fmt.Errorf("render %s: %w", env.name, err) + } + hostnames[env.name] = h + } + + var ingress []kployconfig.IngressHostname + if cfg.Preview != nil && cfg.Preview.Enabled { + ingress, err = kployconfig.RenderPreviewIngress(cfg, 1) + if err != nil { + return err + } + } + + out := c.OutOrStdout() + if outputFormat == "json" { + return renderJSON(out, struct { + Valid bool `json:"valid"` + Project string `json:"project"` + Hostnames map[string]string `json:"hostnames"` + PreviewIngress []kployconfig.IngressHostname `json:"previewIngress,omitempty"` + }{ + Valid: true, + Project: cfg.Project, + Hostnames: hostnames, + PreviewIngress: ingress, + }) + } + + _, _ = fmt.Fprintln(out, "OK") + _, _ = fmt.Fprintln(out) + _, _ = fmt.Fprintln(out, "Sample hostnames:") + tw := tabwriter.NewWriter(out, 0, 0, 2, ' ', 0) + _, _ = fmt.Fprintln(tw, "ENV\tHOSTNAME") + for _, env := range envs { + _, _ = fmt.Fprintf(tw, "%s\t%s\n", env.name, hostnames[env.name]) + } + if err := tw.Flush(); err != nil { + return err + } + + if len(ingress) > 0 { + _, _ = fmt.Fprintln(out) + _, _ = fmt.Fprintln(out, "Example preview env (PR #1):") + tw2 := tabwriter.NewWriter(out, 0, 0, 2, ' ', 0) + _, _ = fmt.Fprintln(tw2, "HOSTNAME\tSERVICE\tPORT") + for _, ing := range ingress { + _, _ = fmt.Fprintf(tw2, "%s\t%s\t%d\n", ing.Hostname, ing.ServiceName, ing.ServicePort) + } + if err := tw2.Flush(); err != nil { + return err + } + } + return nil + }, + } + c.Flags().StringVarP(&file, "file", "f", "kploy.yaml", "Path to kploy.yaml") + return c +} diff --git a/go.mod b/go.mod index 7976906..eaecff0 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.24.3 tool github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen require ( + github.com/bitcomplete/kployconfig v0.1.1 github.com/oapi-codegen/runtime v1.4.0 github.com/spf13/cobra v1.10.1 go.yaml.in/yaml/v3 v3.0.4 diff --git a/go.sum b/go.sum index aec0503..7a8359c 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= +github.com/bitcomplete/kployconfig v0.1.1 h1:3eMolFvB7Jn8/NFdfIatxVwEflvLjJbKxDe+BPCnvfY= +github.com/bitcomplete/kployconfig v0.1.1/go.mod h1:qyAG3qodAW45q7CXYtLe3oBcB5DVL1Q2YtdvJw01QUs= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=