Skip to content

Commit 83bbf0a

Browse files
authored
Support for listing of quotas (#567)
* feat: support for quota listing * feat: integrated review hints
1 parent 4cdc840 commit 83bbf0a

File tree

8 files changed

+461
-1
lines changed

8 files changed

+461
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ Below you can find a list of the STACKIT services already available in the CLI (
7676
| Service | CLI Commands | Status |
7777
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- |
7878
| Observability | `observability` | :white_check_mark: |
79-
| Infrastructure as a Service (IaaS) | `beta network-area` <br/> `beta network` <br/> `beta volume` <br/> `beta network-interface` <br/> `beta public-ip` <br/> `beta security-group` <br/> `beta key-pair` <br/> `beta image` | :white_check_mark: (beta)|
79+
| Infrastructure as a Service (IaaS) | `beta network-area` <br/> `beta network` <br/> `beta volume` <br/> `beta network-interface` <br/> `beta public-ip` <br/> `beta security-group` <br/> `beta key-pair` <br/> `beta image` <br/> `beta quota` | :white_check_mark: (beta)|
8080
| Authorization | `project`, `organization` | :white_check_mark: |
8181
| DNS | `dns` | :white_check_mark: |
8282
| Kubernetes Engine (SKE) | `ske` | :white_check_mark: |

docs/stackit_beta.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ stackit beta [flags]
4747
* [stackit beta network-area](./stackit_beta_network-area.md) - Provides functionality for STACKIT Network Area (SNA)
4848
* [stackit beta network-interface](./stackit_beta_network-interface.md) - Provides functionality for network interfaces
4949
* [stackit beta public-ip](./stackit_beta_public-ip.md) - Provides functionality for public IPs
50+
* [stackit beta quota](./stackit_beta_quota.md) - Manage server quotas
5051
* [stackit beta security-group](./stackit_beta_security-group.md) - Manage security groups
5152
* [stackit beta server](./stackit_beta_server.md) - Provides functionality for servers
5253
* [stackit beta sqlserverflex](./stackit_beta_sqlserverflex.md) - Provides functionality for SQLServer Flex

docs/stackit_beta_quota.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
## stackit beta quota
2+
3+
Manage server quotas
4+
5+
### Synopsis
6+
7+
Manage the lifecycle of server quotas.
8+
9+
```
10+
stackit beta quota [flags]
11+
```
12+
13+
### Options
14+
15+
```
16+
-h, --help Help for "stackit beta quota"
17+
```
18+
19+
### Options inherited from parent commands
20+
21+
```
22+
-y, --assume-yes If set, skips all confirmation prompts
23+
--async If set, runs the command asynchronously
24+
-o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"]
25+
-p, --project-id string Project ID
26+
--region string Target region for region-specific requests
27+
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
28+
```
29+
30+
### SEE ALSO
31+
32+
* [stackit beta](./stackit_beta.md) - Contains beta STACKIT CLI commands
33+
* [stackit beta quota list](./stackit_beta_quota_list.md) - Lists quotas
34+

docs/stackit_beta_quota_list.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
## stackit beta quota list
2+
3+
Lists quotas
4+
5+
### Synopsis
6+
7+
Lists server quotas.
8+
9+
```
10+
stackit beta quota list [flags]
11+
```
12+
13+
### Examples
14+
15+
```
16+
List available quotas
17+
$ stackit beta quota list
18+
```
19+
20+
### Options
21+
22+
```
23+
-h, --help Help for "stackit beta quota list"
24+
```
25+
26+
### Options inherited from parent commands
27+
28+
```
29+
-y, --assume-yes If set, skips all confirmation prompts
30+
--async If set, runs the command asynchronously
31+
-o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"]
32+
-p, --project-id string Project ID
33+
--region string Target region for region-specific requests
34+
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
35+
```
36+
37+
### SEE ALSO
38+
39+
* [stackit beta quota](./stackit_beta_quota.md) - Manage server quotas
40+

internal/cmd/beta/beta.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
networkArea "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-area"
1010
networkinterface "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-interface"
1111
publicip "github.com/stackitcloud/stackit-cli/internal/cmd/beta/public-ip"
12+
"github.com/stackitcloud/stackit-cli/internal/cmd/beta/quota"
1213
securitygroup "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group"
1314
"github.com/stackitcloud/stackit-cli/internal/cmd/beta/server"
1415
"github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex"
@@ -54,4 +55,5 @@ func addSubcommands(cmd *cobra.Command, p *print.Printer) {
5455
cmd.AddCommand(securitygroup.NewCmd(p))
5556
cmd.AddCommand(keypair.NewCmd(p))
5657
cmd.AddCommand(image.NewCmd(p))
58+
cmd.AddCommand(quota.NewCmd(p))
5759
}

internal/cmd/beta/quota/list/list.go

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
package list
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"strconv"
8+
9+
"github.com/goccy/go-yaml"
10+
"github.com/spf13/cobra"
11+
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
12+
"github.com/stackitcloud/stackit-cli/internal/pkg/errors"
13+
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
14+
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
15+
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
16+
"github.com/stackitcloud/stackit-cli/internal/pkg/projectname"
17+
"github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client"
18+
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
19+
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
20+
)
21+
22+
type inputModel struct {
23+
*globalflags.GlobalFlagModel
24+
}
25+
26+
func NewCmd(p *print.Printer) *cobra.Command {
27+
cmd := &cobra.Command{
28+
Use: "list",
29+
Short: "Lists quotas",
30+
Long: "Lists project quotas.",
31+
Args: args.NoArgs,
32+
Example: examples.Build(
33+
examples.NewExample(
34+
`List available quotas`,
35+
`$ stackit beta quota list`,
36+
),
37+
),
38+
RunE: func(cmd *cobra.Command, _ []string) error {
39+
ctx := context.Background()
40+
model, err := parseInput(p, cmd)
41+
if err != nil {
42+
return err
43+
}
44+
45+
// Configure API client
46+
apiClient, err := client.ConfigureClient(p)
47+
if err != nil {
48+
return err
49+
}
50+
51+
projectLabel, err := projectname.GetProjectName(ctx, p, cmd)
52+
if err != nil {
53+
p.Debug(print.ErrorLevel, "get project name: %v", err)
54+
projectLabel = model.ProjectId
55+
}
56+
57+
// Call API
58+
request := buildRequest(ctx, model, apiClient)
59+
60+
response, err := request.Execute()
61+
if err != nil {
62+
return fmt.Errorf("list quotas: %w", err)
63+
}
64+
65+
if items := response.Quotas; items == nil {
66+
p.Info("No quotas found for project %q", projectLabel)
67+
} else {
68+
if err := outputResult(p, model.OutputFormat, items); err != nil {
69+
return fmt.Errorf("output quotas: %w", err)
70+
}
71+
}
72+
73+
return nil
74+
},
75+
}
76+
77+
return cmd
78+
}
79+
80+
func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) {
81+
globalFlags := globalflags.Parse(p, cmd)
82+
if globalFlags.ProjectId == "" {
83+
return nil, &errors.ProjectIdError{}
84+
}
85+
86+
model := inputModel{
87+
GlobalFlagModel: globalFlags,
88+
}
89+
90+
if p.IsVerbosityDebug() {
91+
modelStr, err := print.BuildDebugStrFromInputModel(model)
92+
if err != nil {
93+
p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err)
94+
} else {
95+
p.Debug(print.DebugLevel, "parsed input values: %s", modelStr)
96+
}
97+
}
98+
99+
return &model, nil
100+
}
101+
102+
func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiListQuotasRequest {
103+
request := apiClient.ListQuotas(ctx, model.ProjectId)
104+
105+
return request
106+
}
107+
108+
func outputResult(p *print.Printer, outputFormat string, quotas *iaas.QuotaList) error {
109+
switch outputFormat {
110+
case print.JSONOutputFormat:
111+
details, err := json.MarshalIndent(quotas, "", " ")
112+
if err != nil {
113+
return fmt.Errorf("marshal quota list: %w", err)
114+
}
115+
p.Outputln(string(details))
116+
117+
return nil
118+
case print.YAMLOutputFormat:
119+
details, err := yaml.MarshalWithOptions(quotas, yaml.IndentSequence(true), yaml.UseJSONMarshaler())
120+
if err != nil {
121+
return fmt.Errorf("marshal quota list: %w", err)
122+
}
123+
p.Outputln(string(details))
124+
125+
return nil
126+
127+
default:
128+
table := tables.NewTable()
129+
table.SetHeader("NAME", "LIMIT", "CURRENT USAGE", "PERCENT")
130+
if val := quotas.BackupGigabytes; val != nil {
131+
table.AddRow("Total size in GiB of backups [GiB]", conv(val.GetLimit()), conv(val.GetUsage()), percentage(val))
132+
}
133+
if val := quotas.Backups; val != nil {
134+
table.AddRow("Number of backups [Count]", conv(val.GetLimit()), conv(val.GetUsage()), percentage(val))
135+
}
136+
if val := quotas.Gigabytes; val != nil {
137+
table.AddRow("Total size in GiB of volumes and snapshots [GiB]", conv(val.GetLimit()), conv(val.GetUsage()), percentage(val))
138+
}
139+
if val := quotas.Networks; val != nil {
140+
table.AddRow("Number of networks [Count]", conv(val.GetLimit()), conv(val.GetUsage()), percentage(val))
141+
}
142+
if val := quotas.Nics; val != nil {
143+
table.AddRow("Number of network interfaces (nics) [Count]", conv(val.GetLimit()), conv(val.GetUsage()), percentage(val))
144+
}
145+
if val := quotas.PublicIps; val != nil {
146+
table.AddRow("Number of public IP addresses [Count]", conv(val.GetLimit()), conv(val.GetUsage()), percentage(val))
147+
}
148+
if val := quotas.Ram; val != nil {
149+
table.AddRow("Amount of server RAM in MiB [MiB]", conv(val.GetLimit()), conv(val.GetUsage()), percentage(val))
150+
}
151+
if val := quotas.SecurityGroupRules; val != nil {
152+
table.AddRow("Number of security group rules [Count]", conv(val.GetLimit()), conv(val.GetUsage()), percentage(val))
153+
}
154+
if val := quotas.SecurityGroups; val != nil {
155+
table.AddRow("Number of security groups [Count]", conv(val.GetLimit()), conv(val.GetUsage()), percentage(val))
156+
}
157+
if val := quotas.Snapshots; val != nil {
158+
table.AddRow("Number of snapshots [Count]", conv(val.GetLimit()), conv(val.GetUsage()), percentage(val))
159+
}
160+
if val := quotas.Vcpu; val != nil {
161+
table.AddRow("Number of server cores (vcpu) [Count]", conv(val.GetLimit()), conv(val.GetUsage()), percentage(val))
162+
}
163+
if val := quotas.Volumes; val != nil {
164+
table.AddRow("Number of volumes [Count]", conv(val.GetLimit()), conv(val.GetUsage()), percentage(val))
165+
}
166+
err := table.Display(p)
167+
if err != nil {
168+
return fmt.Errorf("render table: %w", err)
169+
}
170+
171+
return nil
172+
}
173+
}
174+
175+
func conv(n *int64) string {
176+
if n != nil {
177+
return strconv.FormatInt(*n, 10)
178+
}
179+
return "n/a"
180+
}
181+
182+
func percentage(val interface {
183+
GetLimit() *int64
184+
GetUsage() *int64
185+
}) string {
186+
if a, b := val.GetLimit(), val.GetUsage(); a != nil && b != nil {
187+
return fmt.Sprintf("%3.1f%%", 100.0/float64(*a)*float64(*b))
188+
}
189+
return "n/a"
190+
}

0 commit comments

Comments
 (0)