Skip to content

Support for listing of quotas #567

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ Below you can find a list of the STACKIT services already available in the CLI (
| Service | CLI Commands | Status |
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- |
| Observability | `observability` | :white_check_mark: |
| 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)|
| 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)|
| Authorization | `project`, `organization` | :white_check_mark: |
| DNS | `dns` | :white_check_mark: |
| Kubernetes Engine (SKE) | `ske` | :white_check_mark: |
Expand Down
1 change: 1 addition & 0 deletions docs/stackit_beta.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ stackit beta [flags]
* [stackit beta network-area](./stackit_beta_network-area.md) - Provides functionality for STACKIT Network Area (SNA)
* [stackit beta network-interface](./stackit_beta_network-interface.md) - Provides functionality for network interfaces
* [stackit beta public-ip](./stackit_beta_public-ip.md) - Provides functionality for public IPs
* [stackit beta quota](./stackit_beta_quota.md) - Manage server quotas
* [stackit beta security-group](./stackit_beta_security-group.md) - Manage security groups
* [stackit beta server](./stackit_beta_server.md) - Provides functionality for servers
* [stackit beta sqlserverflex](./stackit_beta_sqlserverflex.md) - Provides functionality for SQLServer Flex
Expand Down
34 changes: 34 additions & 0 deletions docs/stackit_beta_quota.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
## stackit beta quota

Manage server quotas

### Synopsis

Manage the lifecycle of server quotas.

```
stackit beta quota [flags]
```

### Options

```
-h, --help Help for "stackit beta quota"
```

### Options inherited from parent commands

```
-y, --assume-yes If set, skips all confirmation prompts
--async If set, runs the command asynchronously
-o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"]
-p, --project-id string Project ID
--region string Target region for region-specific requests
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```

### SEE ALSO

* [stackit beta](./stackit_beta.md) - Contains beta STACKIT CLI commands
* [stackit beta quota list](./stackit_beta_quota_list.md) - Lists quotas

40 changes: 40 additions & 0 deletions docs/stackit_beta_quota_list.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
## stackit beta quota list

Lists quotas

### Synopsis

Lists server quotas.

```
stackit beta quota list [flags]
```

### Examples

```
List available quotas
$ stackit beta quota list
```

### Options

```
-h, --help Help for "stackit beta quota list"
```

### Options inherited from parent commands

```
-y, --assume-yes If set, skips all confirmation prompts
--async If set, runs the command asynchronously
-o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"]
-p, --project-id string Project ID
--region string Target region for region-specific requests
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```

### SEE ALSO

* [stackit beta quota](./stackit_beta_quota.md) - Manage server quotas

2 changes: 2 additions & 0 deletions internal/cmd/beta/beta.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
networkArea "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-area"
networkinterface "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-interface"
publicip "github.com/stackitcloud/stackit-cli/internal/cmd/beta/public-ip"
"github.com/stackitcloud/stackit-cli/internal/cmd/beta/quota"
securitygroup "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group"
"github.com/stackitcloud/stackit-cli/internal/cmd/beta/server"
"github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex"
Expand Down Expand Up @@ -54,4 +55,5 @@ func addSubcommands(cmd *cobra.Command, p *print.Printer) {
cmd.AddCommand(securitygroup.NewCmd(p))
cmd.AddCommand(keypair.NewCmd(p))
cmd.AddCommand(image.NewCmd(p))
cmd.AddCommand(quota.NewCmd(p))
}
190 changes: 190 additions & 0 deletions internal/cmd/beta/quota/list/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package list

import (
"context"
"encoding/json"
"fmt"
"strconv"

"github.com/goccy/go-yaml"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/errors"
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/projectname"
"github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
)

type inputModel struct {
*globalflags.GlobalFlagModel
}

func NewCmd(p *print.Printer) *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: "Lists quotas",
Long: "Lists project quotas.",
Args: args.NoArgs,
Example: examples.Build(
examples.NewExample(
`List available quotas`,
`$ stackit beta quota list`,
),
),
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := context.Background()
model, err := parseInput(p, cmd)
if err != nil {
return err
}

// Configure API client
apiClient, err := client.ConfigureClient(p)
if err != nil {
return err
}

projectLabel, err := projectname.GetProjectName(ctx, p, cmd)
if err != nil {
p.Debug(print.ErrorLevel, "get project name: %v", err)
projectLabel = model.ProjectId
}

// Call API
request := buildRequest(ctx, model, apiClient)

response, err := request.Execute()
if err != nil {
return fmt.Errorf("list quotas: %w", err)
}

if items := response.Quotas; items == nil {
p.Info("No quotas found for project %q", projectLabel)
} else {
if err := outputResult(p, model.OutputFormat, items); err != nil {
return fmt.Errorf("output quotas: %w", err)
}
}

return nil
},
}

return cmd
}

func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)
if globalFlags.ProjectId == "" {
return nil, &errors.ProjectIdError{}
}

model := inputModel{
GlobalFlagModel: globalFlags,
}

if p.IsVerbosityDebug() {
modelStr, err := print.BuildDebugStrFromInputModel(model)
if err != nil {
p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err)
} else {
p.Debug(print.DebugLevel, "parsed input values: %s", modelStr)
}
}

return &model, nil
}

func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiListQuotasRequest {
request := apiClient.ListQuotas(ctx, model.ProjectId)

return request
}

func outputResult(p *print.Printer, outputFormat string, quotas *iaas.QuotaList) error {
switch outputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(quotas, "", " ")
if err != nil {
return fmt.Errorf("marshal quota list: %w", err)
}
p.Outputln(string(details))

return nil
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(quotas, yaml.IndentSequence(true), yaml.UseJSONMarshaler())
if err != nil {
return fmt.Errorf("marshal quota list: %w", err)
}
p.Outputln(string(details))

return nil

default:
table := tables.NewTable()
table.SetHeader("NAME", "LIMIT", "CURRENT USAGE", "PERCENT")
if val := quotas.BackupGigabytes; val != nil {
table.AddRow("Total size in GiB of backups [GiB]", conv(val.GetLimit()), conv(val.GetUsage()), percentage(val))
}
if val := quotas.Backups; val != nil {
table.AddRow("Number of backups [Count]", conv(val.GetLimit()), conv(val.GetUsage()), percentage(val))
}
if val := quotas.Gigabytes; val != nil {
table.AddRow("Total size in GiB of volumes and snapshots [GiB]", conv(val.GetLimit()), conv(val.GetUsage()), percentage(val))
}
if val := quotas.Networks; val != nil {
table.AddRow("Number of networks [Count]", conv(val.GetLimit()), conv(val.GetUsage()), percentage(val))
}
if val := quotas.Nics; val != nil {
table.AddRow("Number of network interfaces (nics) [Count]", conv(val.GetLimit()), conv(val.GetUsage()), percentage(val))
}
if val := quotas.PublicIps; val != nil {
table.AddRow("Number of public IP addresses [Count]", conv(val.GetLimit()), conv(val.GetUsage()), percentage(val))
}
if val := quotas.Ram; val != nil {
table.AddRow("Amount of server RAM in MiB [MiB]", conv(val.GetLimit()), conv(val.GetUsage()), percentage(val))
}
if val := quotas.SecurityGroupRules; val != nil {
table.AddRow("Number of security group rules [Count]", conv(val.GetLimit()), conv(val.GetUsage()), percentage(val))
}
if val := quotas.SecurityGroups; val != nil {
table.AddRow("Number of security groups [Count]", conv(val.GetLimit()), conv(val.GetUsage()), percentage(val))
}
if val := quotas.Snapshots; val != nil {
table.AddRow("Number of snapshots [Count]", conv(val.GetLimit()), conv(val.GetUsage()), percentage(val))
}
if val := quotas.Vcpu; val != nil {
table.AddRow("Number of server cores (vcpu) [Count]", conv(val.GetLimit()), conv(val.GetUsage()), percentage(val))
}
if val := quotas.Volumes; val != nil {
table.AddRow("Number of volumes [Count]", conv(val.GetLimit()), conv(val.GetUsage()), percentage(val))
}
err := table.Display(p)
if err != nil {
return fmt.Errorf("render table: %w", err)
}

return nil
}
}

func conv(n *int64) string {
if n != nil {
return strconv.FormatInt(*n, 10)
}
return "n/a"
}

func percentage(val interface {
GetLimit() *int64
GetUsage() *int64
}) string {
if a, b := val.GetLimit(), val.GetUsage(); a != nil && b != nil {
return fmt.Sprintf("%3.1f%%", 100.0/float64(*a)*float64(*b))
}
return "n/a"
}
Loading
Loading