Skip to content

Commit c563f20

Browse files
authored
Merge pull request #11 from apiqube/dev
Dev
2 parents 06497d7 + b2078c5 commit c563f20

52 files changed

Lines changed: 1522 additions & 583 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,8 @@ jobs:
4747
COMMIT=$(git rev-parse --short HEAD)
4848
DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
4949
50-
go build -ldflags="\
51-
-X github.com/apiqube/cli/cmd/cli.version=$TAG \
52-
-X github.com/apiqube/cli/cmd/cli.commit=$COMMIT \
53-
-X github.com/apiqube/cli/cmd/cli.date=$DATE" \
54-
-o qube ./cmd/qube
55-
50+
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
51+
5652
./qube version
5753
mkdir -p bin
5854
mv qube bin/

Taskfile.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,12 @@ tasks:
4040
cmds:
4141
- reflex -r '\.go$$' -s -- sh -c "task build && task run"
4242

43-
go-fmt:
43+
fmt:
4444
desc: 🧹 Cleaning all go code
4545
cmds:
4646
- gofumpt -l -w .
4747

48-
go-lint:
48+
lint:
4949
desc: 🚀 Command for linting code
5050
cmds:
5151
- golangci-lint run ./...

cmd/cli/apply/apply.go

Lines changed: 82 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
package apply
22

33
import (
4+
"errors"
5+
"fmt"
6+
"os"
7+
"strings"
8+
9+
"github.com/apiqube/cli/internal/core/io"
410
"github.com/apiqube/cli/internal/core/manifests"
5-
"github.com/apiqube/cli/internal/core/manifests/loader"
611
"github.com/apiqube/cli/internal/core/store"
12+
"github.com/apiqube/cli/internal/validate"
713
"github.com/apiqube/cli/ui/cli"
814
"github.com/spf13/cobra"
15+
"gopkg.in/yaml.v3"
916
)
1017

1118
func init() {
@@ -14,43 +21,103 @@ func init() {
1421

1522
var Cmd = &cobra.Command{
1623
Use: "apply",
17-
Short: "Apply resources from manifest file",
24+
Short: "Apply resources from manifest files",
25+
Long: "Apply configuration from YAML manifests with validation and version control",
1826
SilenceErrors: true,
1927
SilenceUsage: true,
2028
Run: func(cmd *cobra.Command, args []string) {
2129
file, err := cmd.Flags().GetString("file")
2230
if err != nil {
23-
cli.Errorf("Failed to parse --file: %s", err.Error())
31+
cli.Errorf("Failed to parse input file flag: %v", err)
2432
return
2533
}
2634

2735
cli.Infof("Loading manifests from: %s", file)
28-
29-
loadedMans, cachedMans, err := loader.LoadManifests(file)
36+
loadedMans, cachedMans, err := io.LoadManifests(file)
3037
if err != nil {
31-
cli.Errorf("Failed to load manifests: %s", err.Error())
38+
cli.Errorf("Critical load error:\n%s", formatLoadError(err, file))
39+
return
40+
}
41+
42+
cli.Info("Validating manifests...")
43+
validator := validate.NewManifestValidator(validate.NewValidator(), cli.Instance())
44+
45+
validator.Validate(loadedMans...)
46+
47+
validMans := validator.Valid()
48+
if len(validMans) == 0 {
49+
cli.Warning("No valid manifests to apply")
3250
return
3351
}
3452

35-
printManifestsLoadResult(loadedMans, cachedMans)
53+
printManifestsLoadResult(validMans, cachedMans)
3654

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

42-
cli.Success("Manifests applied successfully")
61+
printPostApplySummary(validMans)
62+
cli.Successf("Successfully applied %d manifests", len(validMans))
4363
},
4464
}
4565

66+
func formatLoadError(err error, file string) string {
67+
if os.IsNotExist(err) {
68+
return fmt.Sprintf("File not found: \n%s- Please check the path and try again", file)
69+
}
70+
var yamlErr *yaml.TypeError
71+
if errors.As(err, &yamlErr) {
72+
return fmt.Sprintf("YAML syntax error:\n%s", indentYAMLError(yamlErr))
73+
}
74+
75+
return err.Error()
76+
}
77+
4678
func printManifestsLoadResult(newMans, cachedMans []manifests.Manifest) {
47-
for _, m := range newMans {
48-
cli.Infof("New manifest added: %s (h: %s...)", m.GetID(), cli.ShortHash(m.GetMeta().GetHash()))
79+
if len(newMans) > 0 {
80+
var builder strings.Builder
81+
82+
for _, m := range newMans {
83+
builder.WriteString(fmt.Sprintf("\n- %s %s",
84+
m.GetID(),
85+
fmt.Sprintf("(h: %s)", cli.ShortHash(m.GetMeta().GetHash())),
86+
))
87+
}
88+
89+
cli.Infof("New manifests detected: %s", builder.String())
4990
}
5091

51-
for _, m := range cachedMans {
52-
cli.Infof("Manifest %s unchanged (h: %s...) - using cached version", m.GetID(), cli.ShortHash(m.GetMeta().GetHash()))
92+
if len(cachedMans) > 0 {
93+
var builder strings.Builder
94+
95+
for _, m := range cachedMans {
96+
builder.WriteString(fmt.Sprintf("\n- %s %s",
97+
m.GetID(),
98+
fmt.Sprintf("(h: %s)", cli.ShortHash(m.GetMeta().GetHash())),
99+
))
100+
101+
cli.Infof("Using cached manifest: %s", builder.String())
102+
}
53103
}
104+
}
105+
106+
func printPostApplySummary(mans []manifests.Manifest) {
107+
stats := make(map[string]int)
108+
for _, m := range mans {
109+
stats[m.GetKind()]++
110+
}
111+
112+
var builder strings.Builder
113+
114+
for kind, count := range stats {
115+
builder.WriteString(fmt.Sprintf("\n- %s: %d", kind, count))
116+
}
117+
118+
cli.Infof("Applied manifests by kind: %s", builder.String())
119+
}
54120

55-
cli.Infof("Loaded new manifests\nNew: %d\nCached: %d", len(newMans), len(cachedMans))
121+
func indentYAMLError(err *yaml.TypeError) string {
122+
return " " + strings.Join(err.Errors, "\n ")
56123
}

cmd/cli/check/check.go

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import (
44
"fmt"
55
"strings"
66

7+
"github.com/apiqube/cli/internal/core/io"
8+
79
"github.com/apiqube/cli/ui/cli"
810

911
"github.com/apiqube/cli/internal/core/manifests"
1012
"github.com/apiqube/cli/internal/core/manifests/kinds/plan"
11-
"github.com/apiqube/cli/internal/core/manifests/loader"
1213
runner "github.com/apiqube/cli/internal/core/runner/plan"
1314
"github.com/apiqube/cli/internal/core/store"
1415
"github.com/spf13/cobra"
@@ -30,32 +31,34 @@ var cmdManifestCheck = &cobra.Command{
3031
var cmdPlanCheck = &cobra.Command{
3132
Use: "plan",
3233
Short: "Validate a plan manifest",
33-
RunE: func(cmd *cobra.Command, args []string) error {
34+
Run: func(cmd *cobra.Command, args []string) {
3435
opts, err := parseCheckPlanFlags(cmd, args)
3536
if err != nil {
36-
return uiErrorf("Failed to parse provided values: %v", err)
37+
cli.Errorf("Failed to parse provided values: %v", err)
38+
return
3739
}
3840

3941
if err := validateCheckPlanOptions(opts); err != nil {
40-
return uiErrorf("%s", err.Error())
42+
cli.Errorf("%s", err.Error())
43+
return
4144
}
4245

4346
loadedManifests, err := loadManifests(opts)
4447
if err != nil {
45-
return uiErrorf("Failed to load manifests: %v", err)
48+
cli.Errorf("Failed to load manifests: %v", err)
49+
return
4650
}
4751

4852
planManifest, err := extractPlanManifest(loadedManifests)
4953
if err != nil {
50-
return uiErrorf("Failed to check plan manifest: %v", err)
54+
cli.Errorf("Failed to check plan manifest: %v", err)
5155
}
5256

5357
if err := validatePlan(planManifest); err != nil {
54-
return uiErrorf("Failed to check plan: %v", err)
58+
cli.Errorf("Failed to check plan: %v", err)
5559
}
5660

5761
cli.Successf("Successfully checked plan manifest")
58-
return nil
5962
},
6063
}
6164

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

7073
func init() {
7174
cmdManifestCheck.Flags().String("id", "", "Full manifest ID to check (namespace.kind.name)")
72-
cmdManifestCheck.Flags().String("kind", "", "kind of manifest (e.g., HttpTest, Server, Values)")
75+
cmdManifestCheck.Flags().String("kind", "", "kind of manifest (e.g., HttpTest, Target, Values)")
7376
cmdManifestCheck.Flags().String("name", "", "name of manifest")
7477
cmdManifestCheck.Flags().String("namespace", "", "namespace of manifest")
7578
cmdManifestCheck.Flags().String("file", "", "Path to manifest file to check")
@@ -97,11 +100,6 @@ type (
97100
}
98101
)
99102

100-
func uiErrorf(format string, args ...interface{}) error {
101-
cli.Errorf(format, args...)
102-
return nil
103-
}
104-
105103
func validateCheckPlanOptions(opts *checkPlanOptions) error {
106104
if !opts.flagsSet["id"] &&
107105
!opts.flagsSet["name"] &&
@@ -120,7 +118,7 @@ func loadManifests(opts *checkPlanOptions) ([]manifests.Manifest, error) {
120118
})
121119

122120
case opts.flagsSet["file"]:
123-
loadedMans, _, err := loader.LoadManifests(opts.file)
121+
loadedMans, _, err := io.LoadManifests(opts.file)
124122
if err == nil {
125123
cli.Infof("Manifests from provided path %s loaded", opts.file)
126124
}

cmd/cli/edit/edit.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package edit
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
7+
"github.com/apiqube/cli/internal/core/manifests"
8+
"github.com/apiqube/cli/internal/core/manifests/utils"
9+
"github.com/apiqube/cli/internal/core/store"
10+
"github.com/apiqube/cli/internal/operations"
11+
uicli "github.com/apiqube/cli/ui/cli"
12+
"github.com/spf13/cobra"
13+
)
14+
15+
var Cmd = &cobra.Command{
16+
Use: "edit",
17+
Short: "Edit already saved manifests",
18+
SilenceErrors: true,
19+
SilenceUsage: true,
20+
Run: func(cmd *cobra.Command, args []string) {
21+
opts, err := parseOptions(cmd)
22+
if err != nil {
23+
uicli.Error(err.Error())
24+
return
25+
}
26+
27+
var mans []manifests.Manifest
28+
var man, result manifests.Manifest
29+
30+
uicli.Info("Looking for manifest...")
31+
32+
query := store.NewQuery()
33+
queryFlag := false
34+
35+
if opts.flagsSet["id"] {
36+
mans, err = store.Load(store.LoadOptions{IDs: []string{opts.manifestID}})
37+
} else if opts.flagsSet["name"] {
38+
query.WithExactName(opts.name)
39+
queryFlag = true
40+
} else if opts.flagsSet["hash"] {
41+
query.WithHashPrefix(opts.hashPrefix)
42+
queryFlag = true
43+
}
44+
45+
if queryFlag {
46+
mans, err = store.Search(store.NewQuery())
47+
}
48+
49+
if err != nil {
50+
uicli.Errorf("Failed to load manifest: %s", err.Error())
51+
return
52+
} else if len(mans) == 0 {
53+
uicli.Info("No manifests found matching the criteria")
54+
return
55+
}
56+
57+
man = mans[0]
58+
59+
uicli.Successf("Manifest %s was founded", man.GetID())
60+
uicli.Infof("Loading %s manifest in editing context", man.GetID())
61+
62+
if result, err = operations.Edit(man); err != nil {
63+
if errors.Is(err, operations.ErrFileNotEdited) {
64+
uicli.Infof("Manifest file %s was not edited", man.GetID())
65+
return
66+
}
67+
68+
uicli.Errorf("Failed to edit manifest: %s", err.Error())
69+
return
70+
}
71+
72+
uicli.Info("Preparing manifest for saving")
73+
74+
if content, err := operations.NormalizeYAML(result); err != nil {
75+
uicli.Errorf("Failed to normalize manifest: %s", err.Error())
76+
return
77+
} else {
78+
if hash, err := utils.CalculateContentHash(content); err != nil {
79+
uicli.Errorf("Failed to calculate hash: %s", err.Error())
80+
return
81+
} else {
82+
result.GetMeta().SetHash(hash)
83+
}
84+
}
85+
86+
uicli.Infof("Saving %s manifest in storage", man.GetID())
87+
88+
if err = store.Save(man); err != nil {
89+
uicli.Errorf("Failed to save manifest: %s", err.Error())
90+
return
91+
}
92+
93+
uicli.Successf("Manifest %s successfully saved", man.GetID())
94+
},
95+
}
96+
97+
func init() {
98+
Cmd.Flags().StringP("id", "i", "", "Search and edit manifest by ID")
99+
Cmd.Flags().StringP("name", "n", "", "Search and edit manifest by name")
100+
Cmd.Flags().StringP("hash", "H", "", "Search and edit manifest by hash")
101+
}
102+
103+
type options struct {
104+
manifestID string
105+
name string
106+
hashPrefix string
107+
108+
flagsSet map[string]bool
109+
}
110+
111+
func parseOptions(cmd *cobra.Command) (*options, error) {
112+
opts := &options{
113+
flagsSet: make(map[string]bool),
114+
}
115+
116+
markFlag := func(name string) bool {
117+
if cmd.Flags().Changed(name) {
118+
opts.flagsSet[name] = true
119+
return true
120+
}
121+
return false
122+
}
123+
124+
if markFlag("id") {
125+
opts.manifestID, _ = cmd.Flags().GetString("id")
126+
}
127+
if markFlag("name") {
128+
opts.name, _ = cmd.Flags().GetString("name")
129+
}
130+
if markFlag("hash") {
131+
opts.hashPrefix, _ = cmd.Flags().GetString("hash")
132+
}
133+
134+
if opts.flagsSet["id"] && (opts.flagsSet["name"] || opts.flagsSet["hash"]) {
135+
return nil, fmt.Errorf("id/name and hash flags cannot be used together")
136+
}
137+
138+
return opts, nil
139+
}

0 commit comments

Comments
 (0)