Skip to content

Commit 9d4ff40

Browse files
committed
cmd/controller: add wrappers and change workflow
1 parent a1deb84 commit 9d4ff40

12 files changed

Lines changed: 720 additions & 600 deletions

File tree

cmd/cli/apply.go

Lines changed: 11 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
package cli
22

33
import (
4-
"bytes"
54
"encoding/json"
6-
"io"
75

86
"log"
9-
"net/http"
107
"os"
118

129
"cloudstackctl/pkg/handlers"
@@ -44,63 +41,26 @@ var applyCmd = &cobra.Command{
4441
if err := json.Unmarshal(jsonData, &meta); err != nil {
4542
log.Fatalf("Invalid resource JSON: %v", err)
4643
}
47-
kind, _ := meta["kind"].(string)
4844

49-
// Decide whether resource is managed (controller) or unmanaged (local handlers).
50-
// Managed kinds are stored/controlled by the controller and should be
51-
// POSTed to it; unmanaged kinds are handled locally by handlers.
52-
isManaged := func(kind string) bool {
53-
switch kind {
54-
case "Application", "Component", "VirtualMachineSpec":
55-
return true
56-
default:
57-
return false
45+
// standalone mode: apply the resource directly via handlers
46+
if standalone {
47+
// Application, Component, VirtualMachineSpec are only supported in controller mode
48+
kind, _ := meta["kind"].(string)
49+
if kind == "Application" || kind == "Component" || kind == "VirtualMachineSpec" {
50+
log.Fatalf("%s is not supported in standalone mode", kind)
5851
}
59-
}
60-
61-
// No per-VM managed detection here: standalone VMs are created locally;
62-
// in cluster mode VMs are treated as managed and POSTed to the controller.
63-
64-
// Decide execution path based on standalone flag and managed status.
65-
// local apply helper used in standalone and for unmanaged cluster-mode resources
66-
applyLocal := func() {
6752
if err := handlers.ApplyCloudStackResource(jsonData); err != nil {
6853
log.Fatalf("Local apply failed: %v", err)
6954
}
70-
}
71-
72-
if standalone {
73-
applyLocal()
7455
return
7556
}
7657

77-
// Non-standalone (cluster) mode: managed resources go to controller.
78-
// Treat any VM as managed in cluster mode so controller can reconcile it.
79-
if isManaged(kind) || kind == "VirtualMachine" {
80-
server := os.Getenv("CONTROLLER_ENDPOINT")
81-
if server == "" {
82-
server = "http://localhost:65426"
83-
}
84-
url := server + "/apply"
85-
86-
resp, err := http.Post(url, "application/json", bytes.NewReader(jsonData))
87-
if err != nil {
88-
log.Fatalf("Failed to POST to controller: %v", err)
89-
}
90-
defer resp.Body.Close()
91-
92-
body, _ := io.ReadAll(resp.Body)
93-
if resp.StatusCode >= 300 {
94-
log.Fatalf("Controller returned %d: %s", resp.StatusCode, string(body))
95-
}
96-
97-
log.Println("Resource accepted by controller:", string(body))
98-
return
58+
// cluster mode: apply by POSTing to controller HTTP API
59+
body, err := ControllerRequest("POST", "/apply", jsonData)
60+
if err != nil {
61+
log.Fatalf("Failed to POST to controller: %v", err)
9962
}
100-
101-
// Unmanaged resource in cluster mode: apply locally via shared helper
102-
applyLocal()
103-
return
63+
log.Println("Resource accepted by controller:", string(body))
10464
},
10565
}
10666

cmd/cli/delete.go

Lines changed: 29 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
package cli
22

33
import (
4-
v1 "cloudstackctl/apis/v1"
5-
"cloudstackctl/db"
6-
"cloudstackctl/pkg/cloudstack"
74
"cloudstackctl/pkg/handlers"
85
"encoding/json"
96
"log"
@@ -51,20 +48,23 @@ var deleteCmd = &cobra.Command{
5148
log.Fatal("YAML must contain kind and metadata.name")
5249
}
5350
resourceType = kind
54-
// proceed with delete
55-
switch resourceType {
56-
case "Application":
57-
deleteApplication(name)
58-
case "Component":
59-
deleteComponent(name)
60-
case "VirtualMachine":
61-
deleteVM(name)
62-
case "Network":
63-
if err := handlers.DeleteNetwork(name); err != nil {
64-
log.Fatalf("Failed to delete network: %v", err)
51+
payload := map[string]string{"kind": resourceType, "name": name}
52+
rawPayload, _ := json.Marshal(payload)
53+
54+
if standalone {
55+
// Only unmanaged kinds supported in standalone
56+
if resourceType == "Application" || resourceType == "Component" || resourceType == "VirtualMachineSpec" {
57+
log.Fatalf("'%s' is not supported in standalone mode", resourceType)
58+
}
59+
if err := handlers.DeleteCloudStackResource(rawPayload); err != nil {
60+
log.Fatalf("Local delete failed: %v", err)
6561
}
66-
default:
67-
log.Fatalf("Unsupported resource type: %s", resourceType)
62+
return
63+
}
64+
65+
// Cluster mode: send delete request to controller
66+
if _, err := ControllerRequest("POST", "/delete", rawPayload); err != nil {
67+
log.Fatalf("Controller delete failed: %v", err)
6868
}
6969
return
7070
}
@@ -76,40 +76,22 @@ var deleteCmd = &cobra.Command{
7676
resourceType = args[0]
7777
name = args[1]
7878

79-
// Delete resource based on type
80-
switch resourceType {
81-
case "Application":
82-
deleteApplication(name)
83-
case "Component":
84-
deleteComponent(name)
85-
case "VirtualMachine":
86-
deleteVM(name)
87-
case "Network":
88-
if err := handlers.DeleteNetwork(name); err != nil {
89-
log.Fatalf("Failed to delete network: %v", err)
90-
}
91-
case "Volume":
92-
if err := handlers.DeleteVolume(name); err != nil {
93-
log.Fatalf("Failed to delete volume: %v", err)
94-
}
95-
return
96-
case "SSHKey":
97-
if err := handlers.DeleteSSHKey(name); err != nil {
98-
log.Fatalf("Failed to delete ssh key: %v", err)
99-
}
100-
return
101-
case "SecurityGroup":
102-
if err := handlers.DeleteSecurityGroup(name); err != nil {
103-
log.Fatalf("Failed to delete security group: %v", err)
79+
payload := map[string]string{"kind": resourceType, "name": name}
80+
rawPayload, _ := json.Marshal(payload)
81+
82+
if standalone {
83+
if resourceType == "Application" || resourceType == "Component" || resourceType == "VirtualMachineSpec" {
84+
log.Fatalf("'%s' is not supported in standalone mode", resourceType)
10485
}
105-
return
106-
case "Template":
107-
if err := handlers.DeleteTemplate(name); err != nil {
108-
log.Fatalf("Failed to delete template: %v", err)
86+
if err := handlers.DeleteCloudStackResource(rawPayload); err != nil {
87+
log.Fatalf("Local delete failed: %v", err)
10988
}
11089
return
111-
default:
112-
log.Fatalf("Unsupported resource type: %s", resourceType)
90+
}
91+
92+
// Cluster mode: instruct controller to delete the resource
93+
if _, err := ControllerRequest("POST", "/delete", rawPayload); err != nil {
94+
log.Fatalf("Controller delete failed: %v", err)
11395
}
11496
},
11597
}
@@ -118,128 +100,3 @@ func init() {
118100
rootCmd.AddCommand(deleteCmd)
119101
deleteCmd.Flags().StringP("file", "f", "", "Path to YAML configuration file to delete")
120102
}
121-
122-
// deleteApplication deletes an Application and its dependent resources
123-
func deleteApplication(name string) {
124-
var app v1.Application
125-
if err := db.DB.Where("metadata_name = ?", name).First(&app).Error; err != nil {
126-
log.Fatalf("Application %s not found: %v", name, err)
127-
}
128-
129-
// Delete dependent components first
130-
for _, compRef := range app.Spec.Components {
131-
deleteComponent(compRef.Name)
132-
}
133-
134-
// Delete application from database
135-
if err := db.DB.Delete(&app).Error; err != nil {
136-
log.Fatalf("Failed to delete application %s: %v", name, err)
137-
}
138-
139-
log.Printf("Application %s deleted successfully", name)
140-
}
141-
142-
// deleteComponent deletes a Component and its VMs
143-
func deleteComponent(name string) {
144-
var comp v1.Component
145-
if err := db.DB.Where("metadata_name = ?", name).First(&comp).Error; err != nil {
146-
log.Fatalf("Component %s not found: %v", name, err)
147-
}
148-
149-
// Delete component VMs
150-
var vms []v1.VirtualMachine
151-
if err := db.DB.Where("metadata_labels @> ?", map[string]string{"component": name}).Find(&vms).Error; err != nil {
152-
log.Fatalf("Failed to find VMs for component %s: %v", name, err)
153-
}
154-
155-
for _, vm := range vms {
156-
deleteVM(vm.Metadata.Name)
157-
}
158-
159-
// Delete component from database
160-
if err := db.DB.Delete(&comp).Error; err != nil {
161-
log.Fatalf("Failed to delete component %s: %v", name, err)
162-
}
163-
164-
log.Printf("Component %s deleted successfully", name)
165-
}
166-
167-
// deleteVM deletes a VM from CloudStack and database
168-
func deleteVM(name string) {
169-
var vm v1.VirtualMachine
170-
if err := db.DB.Where("metadata_name = ?", name).First(&vm).Error; err != nil {
171-
log.Fatalf("VM %s not found: %v", name, err)
172-
}
173-
174-
// Delete VM from CloudStack if exists
175-
if vm.Status.CloudStackID != "" {
176-
csClient, err := cloudstack.NewClient()
177-
if err != nil {
178-
log.Printf("Warning: CloudStack client unavailable, skipping CloudStack delete: %v", err)
179-
} else {
180-
params := csClient.VirtualMachine.NewDestroyVirtualMachineParams(vm.Status.CloudStackID)
181-
_, err := csClient.VirtualMachine.DestroyVirtualMachine(params)
182-
if err != nil {
183-
log.Printf("Warning: Failed to delete VM %s from CloudStack: %v", name, err)
184-
}
185-
}
186-
}
187-
188-
// Delete VM from database
189-
if err := db.DB.Delete(&vm).Error; err != nil {
190-
log.Fatalf("Failed to delete VM %s from database: %v", name, err)
191-
}
192-
193-
log.Printf("VM %s deleted successfully", name)
194-
}
195-
196-
// deleteNetwork deletes a Network resource
197-
func deleteNetwork(name string) {
198-
// If standalone mode, try to find and delete in CloudStack directly
199-
if standalone {
200-
cs, err := cloudstack.NewClient()
201-
if err != nil {
202-
log.Fatalf("CloudStack client unavailable: %v", err)
203-
}
204-
params := cs.Network.NewListNetworksParams()
205-
params.SetName(name)
206-
resp, err := cs.Network.ListNetworks(params)
207-
if err != nil {
208-
log.Fatalf("CloudStack network lookup failed: %v", err)
209-
}
210-
if resp == nil || len(resp.Networks) == 0 {
211-
log.Fatalf("Network %s not found in CloudStack", name)
212-
}
213-
nid := resp.Networks[0].Id
214-
delp := cs.Network.NewDeleteNetworkParams(nid)
215-
if _, err := cs.Network.DeleteNetwork(delp); err != nil {
216-
log.Fatalf("Failed to delete Network %s from CloudStack: %v", name, err)
217-
}
218-
log.Printf("Network %s deleted from CloudStack (id=%s)", name, nid)
219-
return
220-
}
221-
222-
// Cluster mode: delete from DB and attempt CloudStack deletion if external ID present
223-
var n v1.Network
224-
if err := db.DB.Where("metadata_name = ?", name).First(&n).Error; err != nil {
225-
log.Fatalf("Network %s not found: %v", name, err)
226-
}
227-
228-
if n.Status.CloudStackID != "" {
229-
cs, err := cloudstack.NewClient()
230-
if err != nil {
231-
log.Printf("Warning: CloudStack client unavailable, skipping external delete: %v", err)
232-
} else {
233-
delp := cs.Network.NewDeleteNetworkParams(n.Status.CloudStackID)
234-
if _, err := cs.Network.DeleteNetwork(delp); err != nil {
235-
log.Printf("Warning: Failed to delete network %s from CloudStack: %v", name, err)
236-
}
237-
}
238-
}
239-
240-
if err := db.DB.Delete(&n).Error; err != nil {
241-
log.Fatalf("Failed to delete network %s from database: %v", name, err)
242-
}
243-
244-
log.Printf("Network %s deleted successfully", name)
245-
}

0 commit comments

Comments
 (0)