Skip to content
Merged

Dev #11

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
8 changes: 2 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,8 @@ jobs:
COMMIT=$(git rev-parse --short HEAD)
DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')

go build -ldflags="\
-X github.com/apiqube/cli/cmd/cli.version=$TAG \
-X github.com/apiqube/cli/cmd/cli.commit=$COMMIT \
-X github.com/apiqube/cli/cmd/cli.date=$DATE" \
-o qube ./cmd/qube

go build -ldflags="-X github.com/apiqube/cli/cmd/cli.version=$TAG -X github.com/apiqube/cli/cmd/cli.commit=$COMMIT -X github.com/apiqube/cli/cmd/cli.date=$DATE" -o qube ./cmd/qube

./qube version
mkdir -p bin
mv qube bin/
Expand Down
4 changes: 2 additions & 2 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ tasks:
cmds:
- reflex -r '\.go$$' -s -- sh -c "task build && task run"

go-fmt:
fmt:
desc: 🧹 Cleaning all go code
cmds:
- gofumpt -l -w .

go-lint:
lint:
desc: 🚀 Command for linting code
cmds:
- golangci-lint run ./...
97 changes: 82 additions & 15 deletions cmd/cli/apply/apply.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
package apply

import (
"errors"
"fmt"
"os"
"strings"

"github.com/apiqube/cli/internal/core/io"
"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/internal/validate"
"github.com/apiqube/cli/ui/cli"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
)

func init() {
Expand All @@ -14,43 +21,103 @@ func init() {

var Cmd = &cobra.Command{
Use: "apply",
Short: "Apply resources from manifest file",
Short: "Apply resources from manifest files",
Long: "Apply configuration from YAML manifests with validation and version control",
SilenceErrors: true,
SilenceUsage: true,
Run: func(cmd *cobra.Command, args []string) {
file, err := cmd.Flags().GetString("file")
if err != nil {
cli.Errorf("Failed to parse --file: %s", err.Error())
cli.Errorf("Failed to parse input file flag: %v", err)
return
}

cli.Infof("Loading manifests from: %s", file)

loadedMans, cachedMans, err := loader.LoadManifests(file)
loadedMans, cachedMans, err := io.LoadManifests(file)
if err != nil {
cli.Errorf("Failed to load manifests: %s", err.Error())
cli.Errorf("Critical load error:\n%s", formatLoadError(err, file))
return
}

cli.Info("Validating manifests...")
validator := validate.NewManifestValidator(validate.NewValidator(), cli.Instance())

validator.Validate(loadedMans...)

validMans := validator.Valid()
if len(validMans) == 0 {
cli.Warning("No valid manifests to apply")
return
}

printManifestsLoadResult(loadedMans, cachedMans)
printManifestsLoadResult(validMans, cachedMans)

if err := store.Save(loadedMans...); err != nil {
cli.Infof("Failed to save manifests: %s", err.Error())
cli.Infof("Saving %d manifests to storage...", len(validMans))
if err := store.Save(validMans...); err != nil {
cli.Errorf("Storage error: -\n%s", err.Error())
return
}

cli.Success("Manifests applied successfully")
printPostApplySummary(validMans)
cli.Successf("Successfully applied %d manifests", len(validMans))
},
}

func formatLoadError(err error, file string) string {
if os.IsNotExist(err) {
return fmt.Sprintf("File not found: \n%s- Please check the path and try again", file)
}
var yamlErr *yaml.TypeError
if errors.As(err, &yamlErr) {
return fmt.Sprintf("YAML syntax error:\n%s", indentYAMLError(yamlErr))
}

return err.Error()
}

func printManifestsLoadResult(newMans, cachedMans []manifests.Manifest) {
for _, m := range newMans {
cli.Infof("New manifest added: %s (h: %s...)", m.GetID(), cli.ShortHash(m.GetMeta().GetHash()))
if len(newMans) > 0 {
var builder strings.Builder

for _, m := range newMans {
builder.WriteString(fmt.Sprintf("\n- %s %s",
m.GetID(),
fmt.Sprintf("(h: %s)", cli.ShortHash(m.GetMeta().GetHash())),
))
}

cli.Infof("New manifests detected: %s", builder.String())
}

for _, m := range cachedMans {
cli.Infof("Manifest %s unchanged (h: %s...) - using cached version", m.GetID(), cli.ShortHash(m.GetMeta().GetHash()))
if len(cachedMans) > 0 {
var builder strings.Builder

for _, m := range cachedMans {
builder.WriteString(fmt.Sprintf("\n- %s %s",
m.GetID(),
fmt.Sprintf("(h: %s)", cli.ShortHash(m.GetMeta().GetHash())),
))

cli.Infof("Using cached manifest: %s", builder.String())
}
}
}

func printPostApplySummary(mans []manifests.Manifest) {
stats := make(map[string]int)
for _, m := range mans {
stats[m.GetKind()]++
}

var builder strings.Builder

for kind, count := range stats {
builder.WriteString(fmt.Sprintf("\n- %s: %d", kind, count))
}

cli.Infof("Applied manifests by kind: %s", builder.String())
}

cli.Infof("Loaded new manifests\nNew: %d\nCached: %d", len(newMans), len(cachedMans))
func indentYAMLError(err *yaml.TypeError) string {
return " " + strings.Join(err.Errors, "\n ")
}
28 changes: 13 additions & 15 deletions cmd/cli/check/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import (
"fmt"
"strings"

"github.com/apiqube/cli/internal/core/io"

"github.com/apiqube/cli/ui/cli"

"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/spf13/cobra"
Expand All @@ -30,32 +31,34 @@ var cmdManifestCheck = &cobra.Command{
var cmdPlanCheck = &cobra.Command{
Use: "plan",
Short: "Validate a plan manifest",
RunE: func(cmd *cobra.Command, args []string) error {
Run: func(cmd *cobra.Command, args []string) {
opts, err := parseCheckPlanFlags(cmd, args)
if err != nil {
return uiErrorf("Failed to parse provided values: %v", err)
cli.Errorf("Failed to parse provided values: %v", err)
return
}

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

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

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

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

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

Expand All @@ -69,7 +72,7 @@ var cmdAllCheck = &cobra.Command{

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("kind", "", "kind of manifest (e.g., HttpTest, Target, 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")
Expand Down Expand Up @@ -97,11 +100,6 @@ type (
}
)

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

func validateCheckPlanOptions(opts *checkPlanOptions) error {
if !opts.flagsSet["id"] &&
!opts.flagsSet["name"] &&
Expand All @@ -120,7 +118,7 @@ func loadManifests(opts *checkPlanOptions) ([]manifests.Manifest, error) {
})

case opts.flagsSet["file"]:
loadedMans, _, err := loader.LoadManifests(opts.file)
loadedMans, _, err := io.LoadManifests(opts.file)
if err == nil {
cli.Infof("Manifests from provided path %s loaded", opts.file)
}
Expand Down
139 changes: 139 additions & 0 deletions cmd/cli/edit/edit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package edit

import (
"errors"
"fmt"

"github.com/apiqube/cli/internal/core/manifests"
"github.com/apiqube/cli/internal/core/manifests/utils"
"github.com/apiqube/cli/internal/core/store"
"github.com/apiqube/cli/internal/operations"
uicli "github.com/apiqube/cli/ui/cli"
"github.com/spf13/cobra"
)

var Cmd = &cobra.Command{
Use: "edit",
Short: "Edit already saved manifests",
SilenceErrors: true,
SilenceUsage: true,
Run: func(cmd *cobra.Command, args []string) {
opts, err := parseOptions(cmd)
if err != nil {
uicli.Error(err.Error())
return
}

var mans []manifests.Manifest
var man, result manifests.Manifest

uicli.Info("Looking for manifest...")

query := store.NewQuery()
queryFlag := false

if opts.flagsSet["id"] {
mans, err = store.Load(store.LoadOptions{IDs: []string{opts.manifestID}})
} else if opts.flagsSet["name"] {
query.WithExactName(opts.name)
queryFlag = true
} else if opts.flagsSet["hash"] {
query.WithHashPrefix(opts.hashPrefix)
queryFlag = true
}

if queryFlag {
mans, err = store.Search(store.NewQuery())
}

if err != nil {
uicli.Errorf("Failed to load manifest: %s", err.Error())
return
} else if len(mans) == 0 {
uicli.Info("No manifests found matching the criteria")
return
}

man = mans[0]

uicli.Successf("Manifest %s was founded", man.GetID())
uicli.Infof("Loading %s manifest in editing context", man.GetID())

if result, err = operations.Edit(man); err != nil {
if errors.Is(err, operations.ErrFileNotEdited) {
uicli.Infof("Manifest file %s was not edited", man.GetID())
return
}

uicli.Errorf("Failed to edit manifest: %s", err.Error())
return
}

uicli.Info("Preparing manifest for saving")

if content, err := operations.NormalizeYAML(result); err != nil {
uicli.Errorf("Failed to normalize manifest: %s", err.Error())
return
} else {
if hash, err := utils.CalculateContentHash(content); err != nil {
uicli.Errorf("Failed to calculate hash: %s", err.Error())
return
} else {
result.GetMeta().SetHash(hash)
}
}

uicli.Infof("Saving %s manifest in storage", man.GetID())

if err = store.Save(man); err != nil {
uicli.Errorf("Failed to save manifest: %s", err.Error())
return
}

uicli.Successf("Manifest %s successfully saved", man.GetID())
},
}

func init() {
Cmd.Flags().StringP("id", "i", "", "Search and edit manifest by ID")
Cmd.Flags().StringP("name", "n", "", "Search and edit manifest by name")
Cmd.Flags().StringP("hash", "H", "", "Search and edit manifest by hash")
}

type options struct {
manifestID string
name string
hashPrefix string

flagsSet map[string]bool
}

func parseOptions(cmd *cobra.Command) (*options, error) {
opts := &options{
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.manifestID, _ = cmd.Flags().GetString("id")
}
if markFlag("name") {
opts.name, _ = cmd.Flags().GetString("name")
}
if markFlag("hash") {
opts.hashPrefix, _ = cmd.Flags().GetString("hash")
}

if opts.flagsSet["id"] && (opts.flagsSet["name"] || opts.flagsSet["hash"]) {
return nil, fmt.Errorf("id/name and hash flags cannot be used together")
}

return opts, nil
}
Loading
Loading