Skip to content

Commit eaefc67

Browse files
committed
controller: apply and delete with yaml file
1 parent fade7e8 commit eaefc67

15 files changed

Lines changed: 313 additions & 171 deletions

cmd/cli/apply.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,21 @@ var applyCmd = &cobra.Command{
4242
log.Fatalf("Invalid resource JSON: %v", err)
4343
}
4444

45+
// Determine kind for logging and mode handling
46+
kind, _ := meta["kind"].(string)
47+
4548
// standalone mode: apply the resource directly via handlers
4649
if standalone {
4750
// Application, Component, VirtualMachineSpec are only supported in controller mode
48-
kind, _ := meta["kind"].(string)
4951
if kind == "Application" || kind == "Component" || kind == "VirtualMachineSpec" {
5052
log.Fatalf("%s is not supported in standalone mode", kind)
5153
}
52-
if err := handlers.ApplyCloudStackResource(jsonData); err != nil {
53-
log.Fatalf("Local apply failed: %v", err)
54+
if id, err := handlers.ApplyCloudStackResource(jsonData); err != nil {
55+
log.Fatalf("Local apply failed for %s: %v", kind, err)
56+
} else {
57+
if id != "" {
58+
log.Printf("Applied %s id=%s", kind, id)
59+
}
5460
}
5561
return
5662
}
@@ -60,7 +66,7 @@ var applyCmd = &cobra.Command{
6066
if err != nil {
6167
log.Fatalf("Failed to POST to controller: %v", err)
6268
}
63-
log.Println("Resource accepted by controller:", string(body))
69+
log.Printf("Controller accepted %s: %s", kind, string(body))
6470
},
6571
}
6672

cmd/cli/delete.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,21 @@ var deleteCmd = &cobra.Command{
5656
if resourceType == "Application" || resourceType == "Component" || resourceType == "VirtualMachineSpec" {
5757
log.Fatalf("'%s' is not supported in standalone mode", resourceType)
5858
}
59-
if err := handlers.DeleteCloudStackResource(rawPayload); err != nil {
59+
if id, err := handlers.DeleteCloudStackResource(rawPayload); err != nil {
6060
log.Fatalf("Local delete failed: %v", err)
61+
} else {
62+
if id != "" {
63+
log.Printf("Deleted %s id=%s", resourceType, id)
64+
}
6165
}
6266
return
6367
}
6468

6569
// Cluster mode: send delete request to controller
66-
if _, err := ControllerRequest("POST", "/delete", rawPayload); err != nil {
70+
if body, err := ControllerRequest("POST", "/delete", rawPayload); err != nil {
6771
log.Fatalf("Controller delete failed: %v", err)
72+
} else {
73+
log.Printf("Controller response for %s: %s", resourceType, string(body))
6874
}
6975
return
7076
}
@@ -83,15 +89,21 @@ var deleteCmd = &cobra.Command{
8389
if resourceType == "Application" || resourceType == "Component" || resourceType == "VirtualMachineSpec" {
8490
log.Fatalf("'%s' is not supported in standalone mode", resourceType)
8591
}
86-
if err := handlers.DeleteCloudStackResource(rawPayload); err != nil {
92+
if id, err := handlers.DeleteCloudStackResource(rawPayload); err != nil {
8793
log.Fatalf("Local delete failed: %v", err)
94+
} else {
95+
if id != "" {
96+
log.Printf("Deleted %s id=%s", resourceType, id)
97+
}
8898
}
8999
return
90100
}
91101

92102
// Cluster mode: instruct controller to delete the resource
93-
if _, err := ControllerRequest("POST", "/delete", rawPayload); err != nil {
103+
if body, err := ControllerRequest("POST", "/delete", rawPayload); err != nil {
94104
log.Fatalf("Controller delete failed: %v", err)
105+
} else {
106+
log.Printf("Controller response for %s: %s", resourceType, string(body))
95107
}
96108
},
97109
}

controller/controller.go

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ func (c *Controller) handleApply(w http.ResponseWriter, r *http.Request) {
8181

8282
kind, _ := meta["kind"].(string)
8383
var applyErr error
84+
var appliedID string
8485

8586
switch kind {
8687
case "VirtualMachineSpec":
@@ -161,7 +162,10 @@ func (c *Controller) handleApply(w http.ResponseWriter, r *http.Request) {
161162
}
162163
applyErr = c.applyVM(&vm)
163164
case "Network", "Volume", "SSHKey", "SecurityGroup", "AffinityGroup", "UserData":
164-
applyErr = handlers.ApplyCloudStackResource(body)
165+
appliedID, applyErr = handlers.ApplyCloudStackResource(body)
166+
if applyErr == nil && appliedID != "" {
167+
log.Printf("Applied %s id=%s", kind, appliedID)
168+
}
165169
default:
166170
http.Error(w, "unsupported kind", http.StatusBadRequest)
167171
return
@@ -173,9 +177,17 @@ func (c *Controller) handleApply(w http.ResponseWriter, r *http.Request) {
173177
return
174178
}
175179

180+
// Build response including created/applied resource id when available
181+
respMap := map[string]string{"status": "success", "message": "resource accepted for reconciliation"}
182+
if appliedID != "" {
183+
respMap["id"] = appliedID
184+
respMap["kind"] = kind
185+
respMap["message"] = "resource applied"
186+
}
187+
b, _ := json.Marshal(respMap)
176188
w.Header().Set("Content-Type", "application/json")
177189
w.WriteHeader(http.StatusOK)
178-
w.Write([]byte(`{"status":"success","message":"resource accepted for reconciliation"}`))
190+
w.Write(b)
179191
}
180192

181193
func (c *Controller) handleReconcile(w http.ResponseWriter, r *http.Request) {
@@ -537,6 +549,25 @@ func (c *Controller) handleDelete(w http.ResponseWriter, r *http.Request) {
537549
w.WriteHeader(http.StatusOK)
538550
w.Write([]byte(`{"status":"deleted"}`))
539551
return
552+
case "Network", "Volume", "SSHKey", "SecurityGroup", "AffinityGroup", "UserData", "Template", "Snapshot":
553+
// Delegate deletes of unmanaged CloudStack resources to handlers
554+
if id, err := handlers.DeleteCloudStackResource(body); err != nil {
555+
http.Error(w, fmt.Sprintf("failed to delete %s: %v", kind, err), http.StatusInternalServerError)
556+
return
557+
} else {
558+
if id != "" {
559+
log.Printf("Deleted %s id=%s", kind, id)
560+
}
561+
respMap := map[string]string{"status": "deleted"}
562+
if id != "" {
563+
respMap["id"] = id
564+
respMap["kind"] = kind
565+
}
566+
b, _ := json.Marshal(respMap)
567+
w.WriteHeader(http.StatusOK)
568+
w.Write(b)
569+
return
570+
}
540571
default:
541572
http.Error(w, "unsupported kind for delete", http.StatusBadRequest)
542573
return
@@ -554,19 +585,41 @@ func (c *Controller) Apply(resource interface{}) error {
554585
return c.applyVMSpec(res)
555586
case *v1.VirtualMachine:
556587
// Default to immediate apply for VirtualMachine resources
557-
return handlers.ApplyVirtualMachineManaged(res, true)
588+
_, err := handlers.ApplyVirtualMachineManaged(res, true)
589+
if err != nil {
590+
return err
591+
}
592+
return nil
558593
case *v1.Network:
559-
return handlers.ApplyNetwork(res)
594+
if _, err := handlers.ApplyNetwork(res); err != nil {
595+
return err
596+
}
597+
return nil
560598
case *v1.Volume:
561-
return handlers.ApplyVolume(res)
599+
if _, err := handlers.ApplyVolume(res); err != nil {
600+
return err
601+
}
602+
return nil
562603
case *v1.SSHKey:
563-
return handlers.ApplySSHKey(res)
604+
if _, err := handlers.ApplySSHKey(res); err != nil {
605+
return err
606+
}
607+
return nil
564608
case *v1.SecurityGroup:
565-
return handlers.ApplySecurityGroup(res)
609+
if _, err := handlers.ApplySecurityGroup(res); err != nil {
610+
return err
611+
}
612+
return nil
566613
case *v1.AffinityGroup:
567-
return handlers.ApplyAffinityGroup(res)
614+
if _, err := handlers.ApplyAffinityGroup(res); err != nil {
615+
return err
616+
}
617+
return nil
568618
case *v1.UserData:
569-
return handlers.ApplyUserData(res)
619+
if _, err := handlers.ApplyUserData(res); err != nil {
620+
return err
621+
}
622+
return nil
570623
default:
571624
return logError("Unsupported resource type: %T", res)
572625
}

controller/reconcile.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,13 @@ func (c *Controller) createComponentVMs(comp *v1.Component, compRef v1.Component
259259
return err
260260
}
261261

262-
if err := handlers.ApplyVirtualMachineManaged(vm, true); err != nil {
262+
if id, err := handlers.ApplyVirtualMachineManaged(vm, true); err != nil {
263263
return err
264+
} else {
265+
if id != "" {
266+
vm.Status.CloudStackID = id
267+
db.DB.Save(vm)
268+
}
264269
}
265270
}
266271

pkg/handlers/affinitygroup.go

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,32 @@ import (
1010

1111
// ApplyAffinityGroup ensures the AffinityGroup exists in CloudStack and creates
1212
// it when missing. It uses the AffinityGroup spec.type to create the group.
13-
func ApplyAffinityGroup(ag *v1.AffinityGroup) error {
13+
func ApplyAffinityGroup(ag *v1.AffinityGroup) (string, error) {
1414
name := ag.Metadata.Name
1515
if name == "" {
16-
return fmt.Errorf("affinitygroup metadata.name is required")
16+
return "", fmt.Errorf("affinitygroup metadata.name is required")
1717
}
1818
client, err := cloudstack.NewClient()
1919
if err != nil {
20-
return fmt.Errorf("failed to create CloudStack client: %w", err)
20+
return "", fmt.Errorf("failed to create CloudStack client: %w", err)
2121
}
2222

2323
// Try to find by name
2424
existing, _, err := client.AffinityGroup.GetAffinityGroupByName(name)
2525
if err == nil && existing != nil {
26-
return fmt.Errorf("affinitygroup %s already exists in CloudStack (id=%s); updates are not supported", name, existing.Id)
26+
return "", fmt.Errorf("affinitygroup %s already exists in CloudStack (id=%s); updates are not supported", name, existing.Id)
2727
}
2828

2929
// Create
3030
if ag.Spec.Type == "" {
31-
return fmt.Errorf("affinitygroup spec.type is required for creation")
31+
return "", fmt.Errorf("affinitygroup spec.type is required for creation")
3232
}
3333
p := client.AffinityGroup.NewCreateAffinityGroupParams(name, ag.Spec.Type)
34-
if _, err := client.AffinityGroup.CreateAffinityGroup(p); err != nil {
35-
return fmt.Errorf("failed to create affinity group: %w", err)
34+
resp, err := client.AffinityGroup.CreateAffinityGroup(p)
35+
if err != nil {
36+
return "", fmt.Errorf("failed to create affinity group: %w", err)
3637
}
37-
log.Printf("Created AffinityGroup %s", name)
38-
return nil
38+
return resp.Id, nil
3939
}
4040

4141
// ListAffinityGroups lists affinity groups in CloudStack.
@@ -95,23 +95,23 @@ func ResolveAffinityGroup(name string) (string, error) {
9595
}
9696

9797
// DeleteAffinityGroup deletes an affinity group by name.
98-
func DeleteAffinityGroup(name string) error {
98+
func DeleteAffinityGroup(name string) (string, error) {
9999
client, err := cloudstack.NewClient()
100100
if err != nil {
101-
return fmt.Errorf("failed to create CloudStack client: %w", err)
101+
return "", fmt.Errorf("failed to create CloudStack client: %w", err)
102102
}
103103
existing, _, err := client.AffinityGroup.GetAffinityGroupByName(name)
104104
if err != nil {
105-
return fmt.Errorf("cloudstack API error: %w", err)
105+
return "", fmt.Errorf("cloudstack API error: %w", err)
106106
}
107107
if existing == nil {
108-
return fmt.Errorf("affinity group %s not found", name)
108+
return "", fmt.Errorf("affinity group %s not found", name)
109109
}
110110
dp := client.AffinityGroup.NewDeleteAffinityGroupParams()
111111
dp.SetId(existing.Id)
112112
if _, err := client.AffinityGroup.DeleteAffinityGroup(dp); err != nil {
113-
return fmt.Errorf("failed to delete affinity group %s: %w", name, err)
113+
return "", fmt.Errorf("failed to delete affinity group %s: %w", name, err)
114114
}
115115
log.Printf("AffinityGroup %s deleted from CloudStack (id=%s)", name, existing.Id)
116-
return nil
116+
return existing.Id, nil
117117
}

pkg/handlers/network.go

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -69,42 +69,42 @@ func DescribeNetwork(name string) (any, error) {
6969
}
7070

7171
// DeleteNetwork deletes a network by name via CloudStack API.
72-
func DeleteNetwork(name string) error {
72+
func DeleteNetwork(name string) (string, error) {
7373
client, err := cloudstack.NewClient()
7474
if err != nil {
75-
return fmt.Errorf("failed to create CloudStack client: %w", err)
75+
return "", fmt.Errorf("failed to create CloudStack client: %w", err)
7676
}
7777
params := client.Network.NewListNetworksParams()
7878
params.SetName(name)
7979
resp, err := client.Network.ListNetworks(params)
8080
if err != nil {
81-
return fmt.Errorf("cloudstack API error: %w", err)
81+
return "", fmt.Errorf("cloudstack API error: %w", err)
8282
}
8383
if resp == nil || len(resp.Networks) == 0 {
84-
return fmt.Errorf("network %s not found", name)
84+
return "", fmt.Errorf("network %s not found", name)
8585
}
8686
nid := resp.Networks[0].Id
8787
delp := client.Network.NewDeleteNetworkParams(nid)
8888
if _, err := client.Network.DeleteNetwork(delp); err != nil {
89-
return fmt.Errorf("failed to delete network %s: %w", name, err)
89+
return "", fmt.Errorf("failed to delete network %s: %w", name, err)
9090
}
9191
log.Printf("Network %s deleted from CloudStack (id=%s)", name, nid)
92-
return nil
92+
return nid, nil
9393
}
9494

9595
// ApplyNetwork applies or updates a Network resource using the CloudStack API.
9696
// It searches for an existing network by name; if none is found, it creates one.
9797
// If an existing network is found, it updates the description when it differs
9898
// from the desired spec.
99-
func ApplyNetwork(netRes *v1.Network) error {
99+
func ApplyNetwork(netRes *v1.Network) (string, error) {
100100
name := netRes.Metadata.Name
101101
if name == "" {
102-
return fmt.Errorf("network metadata.name is required")
102+
return "", fmt.Errorf("network metadata.name is required")
103103
}
104104

105105
client, err := cloudstack.NewClient()
106106
if err != nil {
107-
return fmt.Errorf("failed to create CloudStack client: %w", err)
107+
return "", fmt.Errorf("failed to create CloudStack client: %w", err)
108108
}
109109

110110
// Try to find existing network by name
@@ -113,23 +113,23 @@ func ApplyNetwork(netRes *v1.Network) error {
113113
listParams.SetListall(true)
114114
listResp, err := client.Network.ListNetworks(listParams)
115115
if err != nil {
116-
return fmt.Errorf("failed to list networks: %w", err)
116+
return "", fmt.Errorf("failed to list networks: %w", err)
117117
}
118118

119119
// Not found -> create
120120
if listResp == nil || len(listResp.Networks) == 0 {
121121
if netRes.Spec.NetworkOffering == "" || netRes.Spec.Zone == "" {
122-
return fmt.Errorf("network create requires spec.networkOffering and spec.zone in standalone mode")
122+
return "", fmt.Errorf("network create requires spec.networkOffering and spec.zone in standalone mode")
123123
}
124124
// Resolve zone name to ID; require resolution or return an error.
125125
zoneID, zerr := ResolveZone(netRes.Spec.Zone)
126126
if zerr != nil {
127-
return fmt.Errorf("failed to resolve zone %s: %w", netRes.Spec.Zone, zerr)
127+
return "", fmt.Errorf("failed to resolve zone %s: %w", netRes.Spec.Zone, zerr)
128128
}
129129
// Resolve network offering name to ID; require resolution.
130130
offeringID, offErr := ResolveNetworkOffering(netRes.Spec.NetworkOffering)
131131
if offErr != nil {
132-
return fmt.Errorf("failed to resolve network offering %s: %w", netRes.Spec.NetworkOffering, offErr)
132+
return "", fmt.Errorf("failed to resolve network offering %s: %w", netRes.Spec.NetworkOffering, offErr)
133133
}
134134
createParams := client.Network.NewCreateNetworkParams(name, offeringID, zoneID)
135135
if netRes.Spec.Description != "" {
@@ -182,12 +182,12 @@ func ApplyNetwork(netRes *v1.Network) error {
182182
}
183183
resp, err := client.Network.CreateNetwork(createParams)
184184
if err != nil {
185-
return fmt.Errorf("cloudstack create network error: %w", err)
185+
return "", fmt.Errorf("cloudstack create network error: %w", err)
186186
}
187187
log.Printf("Created Network %s (id=%s)", name, resp.Id)
188-
return nil
188+
return resp.Id, nil
189189
}
190190
// Resource exists — updates are not supported at this time
191191
existing := listResp.Networks[0]
192-
return fmt.Errorf("network %s already exists in CloudStack (id=%s); updates are not supported", name, existing.Id)
192+
return "", fmt.Errorf("network %s already exists in CloudStack (id=%s); updates are not supported", name, existing.Id)
193193
}

0 commit comments

Comments
 (0)