Skip to content

Commit b037f8e

Browse files
committed
refactor: remove applyv2 command and streamline apply functionality
- Removed the applyv2 command and its associated tests to simplify the command structure. - Updated the apply command to utilize a new provider framework, enhancing its functionality. - Introduced a new ProviderContext for better management of context and API interactions. - Cleaned up legacy document handling by removing unused document types and related code. - Improved error handling and logging for better clarity during apply operations.
1 parent 97caf65 commit b037f8e

23 files changed

Lines changed: 714 additions & 2483 deletions

cmd/ctrlc/root/apply/cmd.go

Lines changed: 62 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,27 @@ package apply
33
import (
44
"context"
55
"fmt"
6-
"os"
76
"sort"
8-
"strings"
97

108
"github.com/MakeNowJust/heredoc/v2"
11-
"github.com/bmatcuk/doublestar/v4"
129
"github.com/charmbracelet/log"
1310
"github.com/ctrlplanedev/cli/internal/api"
11+
"github.com/ctrlplanedev/cli/internal/api/providers"
12+
"github.com/ctrlplanedev/cli/internal/api/resolver"
1413
"github.com/fatih/color"
14+
"github.com/google/uuid"
1515
"github.com/spf13/cobra"
1616
"github.com/spf13/viper"
1717
)
1818

1919
// NewApplyCmd creates a new apply command
2020
func NewApplyCmd() *cobra.Command {
2121
var filePatterns []string
22+
var selectorRaw string
2223

2324
cmd := &cobra.Command{
2425
Use: "apply",
25-
Short: "Apply a YAML configuration file to create or update resources",
26+
Short: "Apply a YAML configuration file using the new provider framework",
2627
Long: `Apply a YAML configuration file to create or update resources in Ctrlplane.`,
2728
Example: heredoc.Doc(`
2829
# Apply a single resource file
@@ -34,96 +35,23 @@ func NewApplyCmd() *cobra.Command {
3435
# Apply all YAML files matching a glob pattern
3536
$ ctrlc apply -f "**/*.ctrlc.yaml"
3637
37-
# Apply multiple patterns
38-
$ ctrlc apply -f infra/*.yaml -f apps/*.yaml
39-
40-
# Exclude test files using ! prefix (git-style: last match wins)
41-
$ ctrlc apply -f "**/*.yaml" -f "!**/test*.yaml"
42-
43-
# Exclude multiple patterns
44-
$ ctrlc apply -f "**/*.yaml" -f "!**/test*.yaml" -f "!**/staging/**"
45-
46-
# Re-include a previously excluded file (last pattern wins)
47-
$ ctrlc apply -f "**/*.yaml" -f "!**/test*.yaml" -f "**/important-test.yaml"
38+
# Apply URL
39+
$ ctrlc apply -f https://example.com/config.yaml
4840
`),
4941
SilenceUsage: true,
5042
RunE: func(cmd *cobra.Command, args []string) error {
51-
return runApply(cmd.Context(), filePatterns)
43+
return runApply(cmd.Context(), filePatterns, selectorRaw)
5244
},
5345
}
5446

5547
cmd.Flags().StringArrayVarP(&filePatterns, "file", "f", nil, "Path or glob pattern to YAML files (can be specified multiple times, prefix with ! to exclude)")
48+
cmd.Flags().StringVar(&selectorRaw, "selector", "", "Metadata selector in key=value format to apply to created resources")
5649
cmd.MarkFlagRequired("file")
5750

5851
return cmd
5952
}
6053

61-
// expandGlob expands glob patterns to file paths, supporting ** for recursive matching
62-
// It follows git-style pattern matching where later patterns override earlier ones
63-
// and ! prefix negates (excludes) a pattern
64-
func expandGlob(patterns []string) ([]string, error) {
65-
seen := make(map[string]bool)
66-
var files []string
67-
68-
// Parse patterns into rules - ! prefix means exclude
69-
type patternRule struct {
70-
pattern string
71-
include bool // true = include, false = exclude
72-
}
73-
74-
var rules []patternRule
75-
for _, p := range patterns {
76-
if strings.HasPrefix(p, "!") {
77-
rules = append(rules, patternRule{strings.TrimPrefix(p, "!"), false})
78-
} else {
79-
rules = append(rules, patternRule{p, true})
80-
}
81-
}
82-
83-
// First, collect all potential files from include patterns
84-
candidateFiles := make(map[string]bool)
85-
for _, rule := range rules {
86-
if rule.include {
87-
matches, err := doublestar.FilepathGlob(rule.pattern)
88-
if err != nil {
89-
return nil, fmt.Errorf("invalid glob pattern '%s': %w", rule.pattern, err)
90-
}
91-
for _, match := range matches {
92-
info, err := os.Stat(match)
93-
if err != nil || info.IsDir() {
94-
continue
95-
}
96-
candidateFiles[match] = true
97-
}
98-
}
99-
}
100-
101-
// For each candidate file, evaluate all rules in order - last match wins
102-
for filePath := range candidateFiles {
103-
included := false
104-
for _, rule := range rules {
105-
matched, err := doublestar.PathMatch(rule.pattern, filePath)
106-
if err != nil {
107-
return nil, fmt.Errorf("invalid pattern '%s': %w", rule.pattern, err)
108-
}
109-
if matched {
110-
included = rule.include // last matching rule wins
111-
}
112-
}
113-
if included && !seen[filePath] {
114-
seen[filePath] = true
115-
files = append(files, filePath)
116-
}
117-
}
118-
119-
if len(files) == 0 {
120-
return nil, fmt.Errorf("no files matched patterns")
121-
}
122-
123-
return files, nil
124-
}
125-
126-
func runApply(ctx context.Context, filePatterns []string) error {
54+
func runApply(ctx context.Context, filePatterns []string, selectorRaw string) error {
12755
files, err := expandGlob(filePatterns)
12856
if err != nil {
12957
return err
@@ -143,56 +71,41 @@ func runApply(ctx context.Context, filePatterns []string) error {
14371
}
14472

14573
workspaceID := client.GetWorkspaceID(ctx, workspace)
74+
if workspaceID == uuid.Nil {
75+
return fmt.Errorf("workspace not found: %s", workspace)
76+
}
77+
78+
resolver := resolver.NewAPIResolver(client, workspaceID)
79+
applyCtx := NewProviderContext(ctx, workspaceID.String(), client, resolver)
14680

147-
var documents []Document
81+
var specs []providers.TypedSpec
14882
for _, filePath := range files {
149-
docs, err := ParseFile(filePath)
83+
fileSpecs, err := ParseFile(filePath)
15084
if err != nil {
15185
return fmt.Errorf("failed to parse file %s: %w", filePath, err)
15286
}
153-
documents = append(documents, docs...)
87+
specs = append(specs, fileSpecs...)
15488
}
15589

156-
if len(documents) == 0 {
90+
if len(specs) == 0 {
15791
log.Warn("No resources found in files")
15892
return nil
15993
}
16094

161-
log.Info("Applying resources", "count", len(documents), "files", len(files))
162-
163-
docCtx := NewDocContext(workspaceID.String(), client)
164-
docCtx.Context = ctx
165-
166-
var resourceDocs []*ResourceDocument
167-
var otherDocs []Document
168-
for _, doc := range documents {
169-
if rd, ok := doc.(*ResourceDocument); ok {
170-
resourceDocs = append(resourceDocs, rd)
171-
} else {
172-
otherDocs = append(otherDocs, doc)
173-
}
174-
}
175-
176-
sort.Slice(otherDocs, func(i, j int) bool {
177-
return otherDocs[i].Order() > otherDocs[j].Order()
178-
})
179-
180-
var results []ApplyResult
181-
for _, doc := range otherDocs {
182-
result, err := doc.Apply(docCtx)
95+
if selectorRaw != "" {
96+
selector, err := providers.ParseSelector(selectorRaw)
18397
if err != nil {
184-
log.Error("Failed to apply document", "error", err)
98+
return err
18599
}
186-
results = append(results, result)
100+
applySelectorToSpecs(selector, specs)
187101
}
188102

189-
if len(resourceDocs) > 0 {
190-
resourceResults, err := applyResourcesBatch(docCtx, resourceDocs)
191-
if err != nil {
192-
log.Error("Failed to apply resources batch", "error", err)
193-
}
194-
results = append(results, resourceResults...)
195-
}
103+
log.Info("Applying resources", "count", len(specs), "files", len(files))
104+
105+
sortedSpecs := sortSpecsByOrder(specs)
106+
results := providers.
107+
DefaultProviderEngine.
108+
BatchApply(applyCtx, sortedSpecs, providers.BatchApplyOptions{})
196109

197110
printResults(results)
198111

@@ -205,17 +118,46 @@ func runApply(ctx context.Context, filePatterns []string) error {
205118
return nil
206119
}
207120

208-
func printResults(results []ApplyResult) {
121+
func sortSpecsByOrder(specs []providers.TypedSpec) []providers.TypedSpec {
122+
type orderedSpec struct {
123+
spec providers.TypedSpec
124+
order int
125+
index int
126+
}
127+
128+
ordered := make([]orderedSpec, 0, len(specs))
129+
for idx, spec := range specs {
130+
order := 0
131+
if provider, ok := providers.DefaultProviderEngine.GetProvider(spec.Type); ok {
132+
order = provider.Order()
133+
}
134+
ordered = append(ordered, orderedSpec{spec: spec, order: order, index: idx})
135+
}
136+
137+
sort.Slice(ordered, func(i, j int) bool {
138+
if ordered[i].order == ordered[j].order {
139+
return ordered[i].index < ordered[j].index
140+
}
141+
return ordered[i].order > ordered[j].order
142+
})
143+
144+
sorted := make([]providers.TypedSpec, 0, len(ordered))
145+
for _, item := range ordered {
146+
sorted = append(sorted, item.spec)
147+
}
148+
149+
return sorted
150+
}
151+
152+
func printResults(results []providers.Result) {
209153
fmt.Println()
210154

211-
// Color definitions
212155
green := color.New(color.FgGreen, color.Bold)
213156
red := color.New(color.FgRed, color.Bold)
214157
cyan := color.New(color.FgCyan)
215158
yellow := color.New(color.FgYellow)
216159
dim := color.New(color.Faint)
217160

218-
// Print apply results
219161
for _, r := range results {
220162
if r.Error != nil {
221163
red.Print("✗ ")
@@ -232,7 +174,6 @@ func printResults(results []ApplyResult) {
232174

233175
fmt.Println()
234176

235-
// Count successes and failures
236177
var success, failed int
237178
for _, r := range results {
238179
if r.Error != nil {
@@ -242,7 +183,6 @@ func printResults(results []ApplyResult) {
242183
}
243184
}
244185

245-
// Summary with colors
246186
fmt.Printf("Applied %d resources: ", len(results))
247187
green.Printf("%d succeeded", success)
248188
fmt.Print(", ")

0 commit comments

Comments
 (0)