-
Notifications
You must be signed in to change notification settings - Fork 30
feat(cdn): add cdn client, config, list command #1100
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
base: main
Are you sure you want to change the base?
Changes from 2 commits
863828c
f198a95
d5f4304
8c71293
82f7d49
a87cd60
42d1196
0d7729a
c3095c5
7e50687
9e049a0
cbf7a9f
7296ee0
1235792
568659c
a183846
4f4e0b1
dbfea3d
9d512ea
cd065fd
c5d46b4
50ebc7c
13cb16c
b950c06
0b693ec
d29f75a
e95f987
be30de5
f6a0b1a
8faa48d
8cda53b
4ae1761
0edc3c7
f0a12ee
1580200
3cc971e
784d53a
97cde20
9ccaaf8
e93e347
589ce8f
0908904
2b014bc
fb6d721
a228bda
883acc3
5241898
180fa38
90f05e9
76d91fb
3d0f111
d6160f1
b2a1c41
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package cdn | ||
|
|
||
| import ( | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/cdn/distribution" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/params" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| ) | ||
|
|
||
| func NewCmd(params *params.CmdParams) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "cdn", | ||
| Short: "Manage CDN resources", | ||
| Long: "Manage the lifecycle of CDN resources.", | ||
| Args: args.NoArgs, | ||
| Run: utils.CmdHelp, | ||
| } | ||
| addSubcommands(cmd, params) | ||
| return cmd | ||
| } | ||
|
|
||
| func addSubcommands(cmd *cobra.Command, params *params.CmdParams) { | ||
| cmd.AddCommand(distribution.NewCommand(params)) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| package distribution | ||
|
|
||
| import ( | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/cdn/distribution/list" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/params" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| ) | ||
|
|
||
| func NewCommand(params *params.CmdParams) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "distribution", | ||
| Short: "Manage CDN distributions", | ||
| Long: "Manage the lifecycle of CDN distributions.", | ||
| Args: cobra.NoArgs, | ||
| Run: utils.CmdHelp, | ||
| } | ||
| addSubcommands(cmd, params) | ||
| return cmd | ||
| } | ||
|
|
||
| func addSubcommands(cmd *cobra.Command, params *params.CmdParams) { | ||
| cmd.AddCommand(list.NewCmd(params)) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,173 @@ | ||
| package list | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "strings" | ||
|
|
||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/params" | ||
| "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/flags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/cdn/client" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/tables" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/cdn" | ||
| ) | ||
|
|
||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| SortBy string | ||
| } | ||
|
|
||
| const ( | ||
| sortByFlag = "sort-by" | ||
| ) | ||
|
|
||
| func NewCmd(params *params.CmdParams) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "list", | ||
| Short: "List CDN distributions", | ||
| Long: "List all CDN distributions in your account.", | ||
| Args: args.NoArgs, | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `List all CDN distributions`, | ||
| `$ stackit beta dns distribution list`, | ||
| ), | ||
| examples.NewExample( | ||
| `List all CDN distributions sorted by id`, | ||
| `$ stackit beta dns distribution list --sort-by=id`, | ||
cgoetz-inovex marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() // should this be cancellable? | ||
cgoetz-inovex marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| model, err := parseInput(params.Printer, cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| distributions, err := fetchDistributions(ctx, model, apiClient) | ||
| if err != nil { | ||
| return fmt.Errorf("fetch distributions: %w", err) | ||
| } | ||
|
|
||
| return outputResult(params.Printer, model.OutputFormat, distributions) | ||
| }, | ||
| } | ||
|
|
||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
|
|
||
| var sortByFlagOptions = []string{"id", "created", "updated", "origin-url", "status"} | ||
|
||
|
|
||
| func configureFlags(cmd *cobra.Command) { | ||
| // same default as apiClient | ||
| cmd.Flags().Var(flags.EnumFlag(false, "created", sortByFlagOptions...), sortByFlag, fmt.Sprintf("Sort entries by a specific field, one of %q", sortByFlagOptions)) | ||
| } | ||
|
|
||
| func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
|
|
||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| SortBy: flags.FlagWithDefaultToStringValue(p, cmd, sortByFlag), | ||
| } | ||
|
|
||
| p.DebugInputModel(model) | ||
| return &model, nil | ||
| } | ||
|
|
||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *cdn.APIClient, nextPageID cdn.ListDistributionsResponseGetNextPageIdentifierAttributeType) cdn.ApiListDistributionsRequest { | ||
| req := apiClient.ListDistributions(ctx, model.GlobalFlagModel.ProjectId) | ||
| req = req.SortBy(toAPISortBy(model.SortBy)) | ||
| req = req.PageSize(100) | ||
| if nextPageID != nil { | ||
| req = req.PageIdentifier(*nextPageID) | ||
| } | ||
| return req | ||
| } | ||
|
|
||
| func toAPISortBy(sortBy string) string { | ||
| switch sortBy { | ||
| case "id": | ||
| return "id" | ||
| case "created": | ||
| return "createdAt" | ||
| case "updated": | ||
| return "updatedAt" | ||
| case "origin-url": | ||
| return "originUrl" | ||
| case "status": | ||
| return "status" | ||
| default: | ||
| panic("invalid sortBy value, programmer error") | ||
| } | ||
| } | ||
|
|
||
| func outputResult(p *print.Printer, outputFormat string, distributions []cdn.Distribution) error { | ||
| if distributions == nil { | ||
| distributions = make([]cdn.Distribution, 0) // otherwise prints null in json output | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a good catch indeed! Any chance we can solve this in a central place for all commands?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice idea, I'll investigate this a bit, don't know much about go reflection yet.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reflection is considered a no-no, see https://principles.schwarz/principles/engineering-principles/go/avoid-using-reflection
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With reflection I'd add this in val := reflect.ValueOf(output)
if val.IsNil() {
// when passing a nil slice or map, JSON and YAML marshal to "null"
// so we need to create an empty slice or map to have "[]" or "{}" output instead
switch val.Kind() {
case reflect.Slice:
output = make([]any, 0)
case reflect.Map:
output = make(map[any]any)
default:
// do nothing
}
}The linked document suggests type switches, which would not work here. Another solution without reflection, that comes to mind, would be to introduce specialized
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 Could be an option, please feel free to create a Jira ticket and propose a solution 😊 |
||
| } | ||
| return p.OutputResult(outputFormat, distributions, func() error { | ||
| if len(distributions) == 0 { | ||
| p.Outputln("No CDN distributions found") | ||
| return nil | ||
| } | ||
|
|
||
| table := tables.NewTable() | ||
| table.SetHeader("ID", "REGIONS", "STATUS") | ||
cgoetz-inovex marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| for i := range distributions { | ||
cgoetz-inovex marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| d := &distributions[i] | ||
| regions := make([]string, 0, len(*d.Config.Regions)) | ||
| for _, r := range *d.Config.Regions { | ||
| regions = append(regions, string(r)) | ||
| } | ||
| joinedRegions := strings.Join(regions, ", ") | ||
| table.AddRow( | ||
| utils.PtrString(d.Id), | ||
| joinedRegions, | ||
cgoetz-inovex marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| utils.PtrString(d.Status), | ||
| ) | ||
| } | ||
| err := table.Display(p) | ||
| if err != nil { | ||
| return fmt.Errorf("render table: %w", err) | ||
| } | ||
| return nil | ||
| }) | ||
| } | ||
|
|
||
| func fetchDistributions(ctx context.Context, model *inputModel, apiClient *cdn.APIClient) ([]cdn.Distribution, error) { | ||
| var nextPageID cdn.ListDistributionsResponseGetNextPageIdentifierAttributeType | ||
| var distributions []cdn.Distribution | ||
| for { | ||
| request := buildRequest(ctx, model, apiClient, nextPageID) | ||
| response, err := request.Execute() | ||
| if err != nil { | ||
| return nil, fmt.Errorf("list distributions: %w", err) | ||
| } | ||
| nextPageID = response.NextPageIdentifier | ||
| if response.Distributions != nil { | ||
| distributions = append(distributions, *response.Distributions...) | ||
| } | ||
| if nextPageID == nil { | ||
| break | ||
| } | ||
| } | ||
| return distributions, nil | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.