Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
49 changes: 0 additions & 49 deletions cmd/cli/apply.go

This file was deleted.

58 changes: 58 additions & 0 deletions cmd/cli/apply/apply.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package apply

import (
"github.com/apiqube/cli/internal/core/manifests"
"github.com/apiqube/cli/internal/core/manifests/loader"
"github.com/apiqube/cli/internal/core/store"
"github.com/apiqube/cli/ui"
"github.com/spf13/cobra"
)

func init() {
Cmd.Flags().StringP("file", "f", ".", "Path to manifests file, by default is current")
}

var Cmd = &cobra.Command{
Use: "apply",
Short: "Apply resources from manifest file",
SilenceErrors: true,
SilenceUsage: true,
Run: func(cmd *cobra.Command, args []string) {
file, err := cmd.Flags().GetString("file")
if err != nil {
ui.Errorf("Failed to parse --file: %s", err.Error())
return
}

ui.Printf("Loading manifests from: %s", file)
ui.Spinner(true, "Applying manifests...")
defer ui.Spinner(false)

loadedMans, cachedMans, err := loader.LoadManifests(file)
if err != nil {
ui.Errorf("Failed to load manifests: %s", err.Error())
return
}

printManifestsLoadResult(loadedMans, cachedMans)

if err := store.Save(loadedMans...); err != nil {
ui.Error("Failed to save manifests: " + err.Error())
return
}

ui.Println("Manifests applied successfully")
},
}

func printManifestsLoadResult(newMans, cachedMans []manifests.Manifest) {
ui.Infof("Loaded %d new manifests", len(newMans))

for _, m := range newMans {
ui.Infof("New manifest added: %s (h: %s...)", m.GetID(), ui.ShortHash(m.GetMeta().GetHash()))
}

for _, m := range cachedMans {
ui.Infof("Manifest %s unchanged (h: %s...) - using cached version", m.GetID(), ui.ShortHash(m.GetMeta().GetHash()))
}
}
231 changes: 231 additions & 0 deletions cmd/cli/check/check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
package check

import (
"fmt"
"strings"

"github.com/apiqube/cli/internal/core/manifests"
"github.com/apiqube/cli/internal/core/manifests/kinds/plan"
"github.com/apiqube/cli/internal/core/manifests/loader"
runner "github.com/apiqube/cli/internal/core/runner/plan"
"github.com/apiqube/cli/internal/core/store"
"github.com/apiqube/cli/ui"
"github.com/spf13/cobra"
)

var Cmd = &cobra.Command{
Use: "check",
Short: "Check validity of manifests, plans or full configurations",
}

var cmdManifestCheck = &cobra.Command{
Use: "manifest",
Short: "Validate individual manifests",
RunE: func(cmd *cobra.Command, args []string) error {
return nil
},
}

var cmdPlanCheck = &cobra.Command{
Use: "plan",
Short: "Validate a plan manifest",
RunE: func(cmd *cobra.Command, args []string) error {
opts, err := parseCheckPlanFlags(cmd, args)
if err != nil {
return uiErrorf("Failed to parse provided values: %v", err)
}

if err := validateCheckPlanOptions(opts); err != nil {
return uiErrorf("%s", err.Error())
}

ui.Spinner(true, "Checking manifests...")
defer ui.Spinner(false)

loadedManifests, err := loadManifests(opts)
if err != nil {
return uiErrorf("Failed to load manifests: %v", err)
}

planManifest, err := extractPlanManifest(loadedManifests)
if err != nil {
return uiErrorf("Failed to check plan manifest: %v", err)
}

if err := validatePlan(planManifest); err != nil {
return uiErrorf("Failed to check plan: %v", err)
}

ui.Successf("Successfully checked plan manifest")
return nil
},
}

var cmdAllCheck = &cobra.Command{
Use: "all",
Short: "Validate full manifest set (plan + dependencies + tests)",
RunE: func(cmd *cobra.Command, args []string) error {
return nil
},
}

func init() {
cmdManifestCheck.Flags().String("id", "", "Full manifest ID to check (namespace.kind.name)")
cmdManifestCheck.Flags().String("kind", "", "kind of manifest (e.g., HttpTest, Server, Values)")
cmdManifestCheck.Flags().String("name", "", "name of manifest")
cmdManifestCheck.Flags().String("namespace", "", "namespace of manifest")
cmdManifestCheck.Flags().String("file", "", "Path to manifest file to check")

cmdPlanCheck.Flags().String("id", "", "Full plan ID to check (namespace.Plan.name)")
cmdPlanCheck.Flags().StringP("name", "n", "", "name of plan")
cmdPlanCheck.Flags().StringP("namespace", "s", "", "namespace of manifest")
cmdPlanCheck.Flags().StringP("file", "f", "", "Path to plan.yaml")

cmdAllCheck.Flags().String("path", ".", "Path to directory with manifests to check")

Cmd.AddCommand(cmdManifestCheck)
Cmd.AddCommand(cmdPlanCheck)
Cmd.AddCommand(cmdAllCheck)
}

type (
checkPlanOptions struct {
id string
name string
namespace string
file string

flagsSet map[string]bool
}
)

func uiErrorf(format string, args ...interface{}) error {
ui.Errorf(format, args...)
return nil
}

func validateCheckPlanOptions(opts *checkPlanOptions) error {
if !opts.flagsSet["id"] &&
!opts.flagsSet["name"] &&
!opts.flagsSet["namespace"] &&
!opts.flagsSet["file"] {
return fmt.Errorf("at least one check plan filter must be specified")
}
return nil
}

func loadManifests(opts *checkPlanOptions) ([]manifests.Manifest, error) {
switch {
case opts.flagsSet["id"]:
return store.Load(store.LoadOptions{
IDs: []string{opts.id},
})

case opts.flagsSet["file"]:
loadedMans, _, err := loader.LoadManifests(opts.file)
if err == nil {
ui.Infof("Manifests from provided path %s loaded", opts.file)
}
return loadedMans, err

default:
query := store.NewQuery()
if opts.flagsSet["name"] {
query.WithExactName(opts.name)
}
if opts.flagsSet["namespace"] {
query.WithNamespace(opts.namespace)
}
return store.Search(query)
}
}

func extractPlanManifest(mans []manifests.Manifest) (*plan.Plan, error) {
man, err := findManifestWithKind(manifests.PlanManifestKind, mans)
if err != nil {
return nil, err
}

planManifest, ok := man.(*plan.Plan)
if !ok {
return nil, fmt.Errorf("manifest found but not a plan manifest")
}
return planManifest, nil
}

func validatePlan(planToCheck *plan.Plan) error {
manifestIds := planToCheck.GetAllManifests()
loadedMans, err := store.Load(store.LoadOptions{
IDs: manifestIds,
})
if err != nil {
return err
}

builder := runner.NewPlanManagerBuilder().WithManifests(loadedMans...)
generator := builder.Build()
return generator.CheckPlan(planToCheck)
}

func parseCheckPlanFlags(cmd *cobra.Command, _ []string) (*checkPlanOptions, error) {
opts := &checkPlanOptions{
flagsSet: make(map[string]bool),
}

markFlag := func(name string) bool {
if cmd.Flags().Changed(name) {
opts.flagsSet[name] = true
return true
}
return false
}

if markFlag("id") {
opts.id, _ = cmd.Flags().GetString("id")
}
if markFlag("name") {
opts.name, _ = cmd.Flags().GetString("name")
}
if markFlag("namespace") {
opts.namespace, _ = cmd.Flags().GetString("namespace")
}

if markFlag("file") {
var file string
file, _ = cmd.Flags().GetString("file")
if strings.HasSuffix(file, ".yml") || strings.HasSuffix(file, ".yaml") {
opts.file = file
} else {
return nil, fmt.Errorf("--file flag must end with .yml or .yaml")
}
}

exclusiveFlags := []string{"id", "name", "namespace", "file"}

var usedFlags []string
for _, flag := range exclusiveFlags {
if opts.flagsSet[flag] {
usedFlags = append(usedFlags, "--"+flag)
}
}

if len(usedFlags) > 1 {
return nil, fmt.Errorf(
"conflicting filters: %s\n"+
"these filters cannot be used together, please use only one",
strings.Join(usedFlags, " and "),
)
}

return opts, nil
}

func findManifestWithKind(kind string, mans []manifests.Manifest) (manifests.Manifest, error) {
for i, man := range mans {
if man.GetKind() == kind {
return mans[i], nil
}
}

return nil, fmt.Errorf("expected manifest with %s kind not found", kind)
}
Loading
Loading