Skip to content

Commit a634092

Browse files
committed
cmd/pkg: support Project and get/delete/describe/apply resource with --project
1 parent d1d8abb commit a634092

23 files changed

+580
-192
lines changed

Development.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,13 @@ The controller creates/migrates its database tables on startup and listens on `:
108108
```bash
109109
./cloudstackctl apply -f application.yaml
110110
./cloudstackctl get Application
111+
112+
# Get/describe CloudStack resources (standalone or controller mode)
113+
./cloudstackctl get Project # list all projects
114+
./cloudstackctl get VirtualMachine -p my-project # VMs in a project
115+
./cloudstackctl get VirtualMachine -P # VMs across all projects
116+
./cloudstackctl get VirtualMachine -A # all VMs incl. unmanaged (controller only)
117+
./cloudstackctl describe Network my-net -p my-project
111118
```
112119

113120
---

README.md

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ There are two supported mode:
5252
| AffinityGroup |||
5353
| SSHKey |||
5454
| UserData |||
55+
| Project |||
5556
| Application |||
5657
| Component |||
5758
| VirtualMachineSpec |||
@@ -72,6 +73,7 @@ There are two supported mode:
7273
| **UserData** | userdata, ud, uds | User data scripts for VM initialization | create, update, delete, get, describe | ConfigMap / Secret |
7374
| **AffinityGroup** | ag, affinitygroup, affinitygroups | Host/VM affinity or anti-affinity | create, update, delete, get, describe | PodAffinity / PodAntiAffinity |
7475
| **SecurityGroup** | sg, sgs, securitygroup, securitygroups | Firewall rules for VMs | create, update, delete, get, describe | NetworkPolicy |
76+
| **Project** | proj, projs, project, projects | CloudStack project (resource boundary) | create, delete, get, describe | Namespace |
7577

7678
---
7779

@@ -210,7 +212,9 @@ Usage notes:
210212

211213
| Flag | Short | Description |
212214
|---|---|---|
213-
| `--all` | `-A` | (Controller mode, VirtualMachine only) List all VMs from CloudStack including unmanaged ones. Without this flag only DB-managed VMs are returned. |
215+
| `--all-vms` | `-A` | (Controller mode, VirtualMachine only) List all VMs from CloudStack including unmanaged ones. Without this flag only DB-managed VMs are returned. |
216+
| `--all-projects` | `-P` | List resources across all CloudStack projects (and resources not in any project). |
217+
| `--project` | `-p` | Filter results to a specific CloudStack project by name. |
214218
| `--application` | `-a` | Filter results by application name. Applies to `Application`, `Component`, and `VirtualMachine` resources in controller mode. |
215219

216220
Examples:
@@ -222,11 +226,43 @@ Examples:
222226
# List all VMs from CloudStack (including unmanaged)
223227
./cloudstackctl get VirtualMachine -A
224228
229+
# List VMs in a specific project
230+
./cloudstackctl get VirtualMachine -p my-project
231+
232+
# List VMs across all projects
233+
./cloudstackctl get VirtualMachine -P
234+
235+
# List all projects
236+
./cloudstackctl get Project
237+
225238
# List components belonging to a specific application
226239
./cloudstackctl get Component -a my-app
227240
```
228241

229-
### `reconcile` command
242+
### `describe` command flags
243+
244+
| Flag | Short | Description |
245+
|---|---|---|
246+
| `--all-vms` | `-A` | (Controller mode, VirtualMachine only) Describe a VM directly from CloudStack rather than from the DB. |
247+
| `--project` | `-p` | Look up the resource within a specific CloudStack project by name. |
248+
249+
Examples:
250+
251+
```bash
252+
# Describe a managed VM from the DB
253+
./cloudstackctl describe VirtualMachine my-vm
254+
255+
# Describe a VM directly from CloudStack
256+
./cloudstackctl describe VirtualMachine my-vm -A
257+
258+
# Describe a network inside a project
259+
./cloudstackctl describe Network my-net -p my-project
260+
261+
# Describe a project
262+
./cloudstackctl describe Project my-project
263+
```
264+
265+
### `reconcile` command (TODO)
230266

231267
Trigger on-demand reconciliation for a resource in controller mode:
232268

@@ -345,7 +381,6 @@ Choose the option that fits your environment — full step-by-step instructions
345381

346382
* CLI: Support resource update via YAML file
347383
* CLI: Support Rolling updates
348-
* CLI: Support CloudStack users and projects
349384
* CLI: Multi-zone deployments
350385
* CLI: Security group improvements
351386
* CLI: Support UserData details

apis/v1/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const APIVersion = "cloudstackctl/v1"
1212
// Metadata contains resource metadata (name/labels/annotations)
1313
type Metadata struct {
1414
Name string `json:"name" yaml:"name"`
15+
Project string `json:"project,omitempty" yaml:"project,omitempty" gorm:"column:metadata_project"`
1516
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty" gorm:"serializer:json"`
1617
Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty" gorm:"serializer:json"`
1718
}

cmd/cli/delete.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ var deleteCmd = &cobra.Command{
2020
Run: func(cmd *cobra.Command, args []string) {
2121
// file flag support (delete -f <yaml>)
2222
filePath, _ := cmd.Flags().GetString("file")
23+
projectFlag, _ := cmd.Flags().GetString("project")
2324
var name string
2425
var resourceType string
2526
if filePath != "" {
@@ -54,16 +55,26 @@ var deleteCmd = &cobra.Command{
5455
meta := docs[i]
5556
kind, _ := meta["kind"].(string)
5657
name = ""
58+
project := ""
5759
if m, ok := meta["metadata"].(map[string]interface{}); ok {
5860
if n, ok := m["name"].(string); ok {
5961
name = n
6062
}
63+
if p, ok := m["project"].(string); ok {
64+
project = p
65+
}
66+
}
67+
if project == "" && projectFlag != "" {
68+
project = projectFlag
6169
}
6270
if kind == "" || name == "" {
6371
log.Fatalf("each YAML doc must contain kind and metadata.name")
6472
}
6573
resourceType = kind
66-
payload := map[string]string{"kind": resourceType, "name": name}
74+
payload := map[string]interface{}{"kind": resourceType, "name": name}
75+
if project != "" {
76+
payload["project"] = project
77+
}
6778
rawPayload, _ := json.Marshal(payload)
6879

6980
if standalone {
@@ -108,7 +119,10 @@ var deleteCmd = &cobra.Command{
108119
resourceType = normalizeResourceType(args[0])
109120
name = args[1]
110121

111-
payload := map[string]string{"kind": resourceType, "name": name}
122+
payload := map[string]interface{}{"kind": resourceType, "name": name}
123+
if projectFlag != "" {
124+
payload["project"] = projectFlag
125+
}
112126
rawPayload, _ := json.Marshal(payload)
113127

114128
if standalone {
@@ -147,4 +161,5 @@ var deleteCmd = &cobra.Command{
147161
func init() {
148162
rootCmd.AddCommand(deleteCmd)
149163
deleteCmd.Flags().StringP("file", "f", "", "Path to YAML configuration file to delete")
164+
deleteCmd.Flags().StringP("project", "p", "", "Delete resource within a specific CloudStack project")
150165
}

cmd/cli/describe.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,17 @@ var describeCmd = &cobra.Command{
2626

2727
// Certain managed kinds are only supported via the controller
2828
if standalone {
29+
if describeAllVMs {
30+
log.Fatal("--all-vms/-A is supported in controller mode only")
31+
}
2932
if resourceType == "Application" || resourceType == "Component" || resourceType == "VirtualMachineSpec" {
3033
log.Fatalf("'%s' is not supported in standalone mode", resourceType)
3134
}
3235
// Standalone: use local describe wrapper
33-
payload := map[string]string{"kind": resourceType, "name": name}
36+
payload := map[string]interface{}{"kind": resourceType, "name": name}
37+
if describeProject != "" {
38+
payload["project"] = describeProject
39+
}
3440
raw, _ := json.Marshal(payload)
3541
if respAny, err := handlers.DescribeCloudStackResource(raw); err != nil {
3642
log.Fatalf("Local describe failed: %v", err)
@@ -54,8 +60,11 @@ var describeCmd = &cobra.Command{
5460
q := url.Values{}
5561
q.Set("kind", resourceType)
5662
q.Set("name", name)
57-
if describeAll {
58-
q.Set("all", "true")
63+
if describeProject != "" {
64+
q.Set("project", describeProject)
65+
}
66+
if describeAllVMs && resourceType == "VirtualMachine" {
67+
q.Set("all-vms", "true")
5968
}
6069
path := "/describe?" + q.Encode()
6170
body, err := ControllerRequest("GET", path, nil)
@@ -81,8 +90,10 @@ func init() {
8190
rootCmd.AddCommand(describeCmd)
8291
}
8392

84-
var describeAll bool
93+
var describeAllVMs bool
94+
var describeProject string
8595

8696
func init() {
87-
describeCmd.Flags().BoolVarP(&describeAll, "all", "A", false, "Describe a VM from CloudStack (controller mode only)")
97+
describeCmd.Flags().BoolVarP(&describeAllVMs, "all-vms", "A", false, "Describe a VM directly from CloudStack (controller mode only)")
98+
describeCmd.Flags().StringVarP(&describeProject, "project", "p", "", "Look up resource within a specific CloudStack project")
8899
}

cmd/cli/get.go

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,25 @@ var getCmd = &cobra.Command{
3939
// Standalone: call local handler wrapper. If any requested kind is
4040
// managed by the controller, fail in standalone mode.
4141
if standalone {
42+
if getAllVMs {
43+
log.Fatal("--all-vms/-A is supported in controller mode only")
44+
}
4245
for _, resourceType := range kinds {
4346
if resourceType == "Application" || resourceType == "Component" || resourceType == "VirtualMachineSpec" {
4447
log.Fatalf("'%s' is not supported in standalone mode", resourceType)
4548
}
4649
}
4750
for i, resourceType := range kinds {
48-
payload := map[string]string{"kind": resourceType}
51+
payload := map[string]interface{}{"kind": resourceType}
4952
if name != "" {
5053
payload["name"] = name
5154
}
55+
if getProject != "" {
56+
payload["project"] = getProject
57+
}
58+
if getAllProjects {
59+
payload["allProjects"] = true
60+
}
5261
raw, _ := json.Marshal(payload)
5362
if resp, err := handlers.GetCloudStackResource(raw); err != nil {
5463
log.Fatalf("Local get failed: %v", err)
@@ -67,13 +76,19 @@ var getCmd = &cobra.Command{
6776
for i, resourceType := range kinds {
6877
var endpoint = "/list"
6978
q := url.Values{}
70-
if getAll && resourceType == "VirtualMachine" {
71-
q.Set("all", "true")
72-
}
7379
q.Set("kind", resourceType)
7480
if name != "" {
7581
q.Set("name", name)
7682
}
83+
if getProject != "" {
84+
q.Set("project", getProject)
85+
}
86+
if getAllProjects {
87+
q.Set("all-projects", "true")
88+
}
89+
if getAllVMs && resourceType == "VirtualMachine" {
90+
q.Set("all-vms", "true")
91+
}
7792
if getApplication != "" && (resourceType == "Application" || resourceType == "Component" || resourceType == "VirtualMachine") {
7893
q.Set("application", getApplication)
7994
}
@@ -87,8 +102,9 @@ var getCmd = &cobra.Command{
87102
fmt.Printf("\n")
88103
}
89104

90-
// If controller returned VM objects from DB, pretty-print them
91-
if resourceType == "VirtualMachine" && !getAll {
105+
// DB-backed VMs are shown in controller mode unless CloudStack-scoped
106+
// VM listing is explicitly requested.
107+
if resourceType == "VirtualMachine" && !getAllVMs && getProject == "" && !getAllProjects {
92108
var vms []v1.VirtualMachine
93109
if err := json.Unmarshal(body, &vms); err == nil {
94110
handlers.PrintVMsFromController(vms)
@@ -136,11 +152,15 @@ func init() {
136152
rootCmd.AddCommand(getCmd)
137153
}
138154

139-
var getAll bool
155+
var getAllVMs bool
156+
var getAllProjects bool
157+
var getProject string
140158
var getApplication string
141159

142160
func init() {
143-
getCmd.Flags().BoolVarP(&getAll, "all", "A", false, "Show all VMs from CloudStack (include unmanaged) — controller mode only")
161+
getCmd.Flags().BoolVarP(&getAllVMs, "all-vms", "A", false, "Show all VMs from CloudStack (include unmanaged) - controller mode only")
162+
getCmd.Flags().BoolVarP(&getAllProjects, "all-projects", "P", false, "List resources across all projects (and no project)")
163+
getCmd.Flags().StringVarP(&getProject, "project", "p", "", "Filter results by project name")
144164
getCmd.Flags().StringVarP(&getApplication, "application", "a", "", "Filter Application/Component/VirtualMachine results by application name")
145165
}
146166

@@ -199,6 +219,12 @@ func tryDecodeAndPrint(resourceType string, body []byte) bool {
199219
handlers.PrintCloudStackResource(resourceType, &resp)
200220
return true
201221
}
222+
case "Project":
223+
var resp cs.ListProjectsResponse
224+
if err := json.Unmarshal(body, &resp); err == nil {
225+
handlers.PrintCloudStackResource(resourceType, &resp)
226+
return true
227+
}
202228
}
203229
return false
204230
}

cmd/cli/utils.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ func normalizeResourceType(s string) string {
3333
return "AffinityGroup"
3434
case "sg", "sgs", "securitygroup", "securitygroups":
3535
return "SecurityGroup"
36+
case "proj", "projs", "project", "projects":
37+
return "Project"
3638
default:
3739
return s
3840
}

0 commit comments

Comments
 (0)