Skip to content

Commit f25a2b2

Browse files
committed
feat: add chaning feature for apply command
Signed-off-by: Youngjin Jo <[email protected]>
1 parent ddb6463 commit f25a2b2

File tree

1 file changed

+113
-43
lines changed

1 file changed

+113
-43
lines changed

cmd/other/apply.go

Lines changed: 113 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
/*
2-
Copyright © 2025 NAME HERE <EMAIL ADDRESS>
3-
*/
41
package other
52

63
import (
4+
"bytes"
75
"encoding/json"
86
"fmt"
7+
"io"
98
"os"
9+
"strings"
1010

1111
"github.com/cloudforet-io/cfctl/pkg/transport"
1212
"github.com/pterm/pterm"
@@ -21,76 +21,146 @@ type ResourceSpec struct {
2121
Spec map[string]interface{} `yaml:"spec"`
2222
}
2323

24+
func parseResourceSpecs(data []byte) ([]ResourceSpec, error) {
25+
var resources []ResourceSpec
26+
27+
// Split YAML documents
28+
decoder := yaml.NewDecoder(bytes.NewReader(data))
29+
for {
30+
var resource ResourceSpec
31+
if err := decoder.Decode(&resource); err != nil {
32+
if err == io.EOF {
33+
break
34+
}
35+
return nil, fmt.Errorf("failed to parse YAML: %v", err)
36+
}
37+
resources = append(resources, resource)
38+
}
39+
40+
return resources, nil
41+
}
42+
2443
// ApplyCmd represents the apply command
2544
var ApplyCmd = &cobra.Command{
2645
Use: "apply",
2746
Short: "Apply a configuration to a resource using a file",
2847
Long: `Apply the configuration in the YAML file to create or update a resource`,
29-
Example: ` # Create test.yaml
48+
Example: ` # 01. Create a test.yaml file with service-verb-resource-spec format
3049
service: identity
3150
verb: create
32-
resource: user
51+
resource: WorkspaceGroup
52+
spec:
53+
name: Test Workspace Group
54+
---
55+
service: identity
56+
verb: add_users
57+
resource: WorkspaceGroup
3358
spec:
34-
user_id: test-user
35-
auth_type: LOCAL
59+
workspace_group_id: wg-12345
60+
users:
61+
- user_id: u-123
62+
role_id: role-123
63+
- user_id: u-456
64+
role_id: role-456
3665
37-
# Apply the configuration in test.yaml
38-
$ cfctl apply -f test.yaml`,
66+
# 02. Apply the configuration
67+
cfctl apply -f test.yaml`,
3968
RunE: func(cmd *cobra.Command, args []string) error {
4069
filename, _ := cmd.Flags().GetString("filename")
4170
if filename == "" {
4271
return fmt.Errorf("filename is required (-f flag)")
4372
}
4473

45-
// Read and parse YAML file
74+
// Read YAML file
4675
data, err := os.ReadFile(filename)
4776
if err != nil {
4877
return fmt.Errorf("failed to read file: %v", err)
4978
}
5079

51-
var resource ResourceSpec
52-
if err := yaml.Unmarshal(data, &resource); err != nil {
53-
return fmt.Errorf("failed to parse YAML: %v", err)
80+
// Parse all resource specs
81+
resources, err := parseResourceSpecs(data)
82+
if err != nil {
83+
return err
5484
}
5585

56-
// Convert spec to parameters
57-
var parameters []string
58-
for key, value := range resource.Spec {
59-
switch v := value.(type) {
60-
case string:
61-
parameters = append(parameters, fmt.Sprintf("%s=%s", key, v))
62-
case bool, int, float64:
63-
parameters = append(parameters, fmt.Sprintf("%s=%v", key, v))
64-
case []interface{}, map[string]interface{}:
65-
// For arrays and maps, convert to JSON string
66-
jsonBytes, err := json.Marshal(v)
67-
if err != nil {
68-
return fmt.Errorf("failed to marshal parameter %s: %v", key, err)
69-
}
70-
parameters = append(parameters, fmt.Sprintf("%s=%s", key, string(jsonBytes)))
71-
default:
72-
// For other complex types, try JSON marshaling
73-
jsonBytes, err := json.Marshal(v)
74-
if err != nil {
75-
return fmt.Errorf("failed to marshal parameter %s: %v", key, err)
86+
// Process each resource sequentially
87+
var lastResponse map[string]interface{}
88+
for i, resource := range resources {
89+
pterm.Info.Printf("Applying resource %d/%d: %s/%s\n",
90+
i+1, len(resources), resource.Service, resource.Resource)
91+
92+
// Convert spec to parameters
93+
parameters := convertSpecToParameters(resource.Spec, lastResponse)
94+
95+
options := &transport.FetchOptions{
96+
Parameters: parameters,
97+
}
98+
99+
response, err := transport.FetchService(resource.Service, resource.Verb, resource.Resource, options)
100+
if err != nil {
101+
pterm.Error.Printf("Failed to apply resource %d/%d: %v\n", i+1, len(resources), err)
102+
return err
103+
}
104+
105+
lastResponse = response
106+
pterm.Success.Printf("Resource %d/%d applied successfully\n", i+1, len(resources))
107+
}
108+
109+
return nil
110+
},
111+
}
112+
113+
func convertSpecToParameters(spec map[string]interface{}, lastResponse map[string]interface{}) []string {
114+
var parameters []string
115+
116+
for key, value := range spec {
117+
switch v := value.(type) {
118+
case string:
119+
// Check if value references previous response
120+
if strings.HasPrefix(v, "${") && strings.HasSuffix(v, "}") {
121+
refPath := strings.Trim(v, "${}")
122+
if val := getValueFromPath(lastResponse, refPath); val != "" {
123+
parameters = append(parameters, fmt.Sprintf("%s=%s", key, val))
76124
}
125+
} else {
126+
parameters = append(parameters, fmt.Sprintf("%s=%s", key, v))
127+
}
128+
case []interface{}, map[string]interface{}:
129+
jsonBytes, err := json.Marshal(v)
130+
if err == nil {
77131
parameters = append(parameters, fmt.Sprintf("%s=%s", key, string(jsonBytes)))
78132
}
133+
default:
134+
parameters = append(parameters, fmt.Sprintf("%s=%v", key, v))
79135
}
136+
}
80137

81-
options := &transport.FetchOptions{
82-
Parameters: parameters,
83-
}
138+
return parameters
139+
}
84140

85-
_, err = transport.FetchService(resource.Service, resource.Verb, resource.Resource, options)
86-
if err != nil {
87-
pterm.Error.Println(err.Error())
88-
return nil
141+
func getValueFromPath(data map[string]interface{}, path string) string {
142+
parts := strings.Split(path, ".")
143+
current := data
144+
145+
for _, part := range parts {
146+
if v, ok := current[part]; ok {
147+
switch val := v.(type) {
148+
case map[string]interface{}:
149+
current = val
150+
case string:
151+
return val
152+
default:
153+
if str, err := json.Marshal(val); err == nil {
154+
return string(str)
155+
}
156+
return fmt.Sprintf("%v", val)
157+
}
158+
} else {
159+
return ""
89160
}
161+
}
90162

91-
pterm.Success.Printf("Resource %s/%s applied successfully\n", resource.Service, resource.Resource)
92-
return nil
93-
},
163+
return ""
94164
}
95165

96166
func init() {

0 commit comments

Comments
 (0)