Skip to content

Commit 2a961df

Browse files
Postgresflex option command (#79)
* Postgresflex options * Update docs
1 parent 78075a0 commit 2a961df

File tree

3 files changed

+572
-0
lines changed

3 files changed

+572
-0
lines changed
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
package options
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
8+
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
9+
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
10+
"github.com/stackitcloud/stackit-cli/internal/pkg/flags"
11+
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
12+
"github.com/stackitcloud/stackit-cli/internal/pkg/pager"
13+
"github.com/stackitcloud/stackit-cli/internal/pkg/services/postgresflex/client"
14+
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
15+
16+
"github.com/spf13/cobra"
17+
"github.com/stackitcloud/stackit-sdk-go/services/postgresflex"
18+
)
19+
20+
const (
21+
flavorsFlag = "flavors"
22+
versionsFlag = "versions"
23+
storagesFlag = "storages"
24+
flavorIdFlag = "flavor-id"
25+
)
26+
27+
type inputModel struct {
28+
*globalflags.GlobalFlagModel
29+
30+
Flavors bool
31+
Versions bool
32+
Storages bool
33+
FlavorId *string
34+
}
35+
36+
type options struct {
37+
Flavors *[]postgresflex.Flavor `json:"flavors,omitempty"`
38+
Versions *[]string `json:"versions,omitempty"`
39+
Storages *flavorStorages `json:"flavorStorages,omitempty"`
40+
}
41+
42+
type flavorStorages struct {
43+
FlavorId string `json:"flavorId"`
44+
Storages *postgresflex.ListStoragesResponse `json:"storages"`
45+
}
46+
47+
func NewCmd() *cobra.Command {
48+
cmd := &cobra.Command{
49+
Use: "options",
50+
Short: "Lists PostgreSQL Flex options",
51+
Long: "Lists PostgreSQL Flex options (flavors, versions and storages for a given flavor)\nPass one or more flags to filter what categories are shown.",
52+
Args: args.NoArgs,
53+
Example: examples.Build(
54+
examples.NewExample(
55+
`List PostgreSQL Flex flavors options`,
56+
"$ stackit postgresflex options --flavors"),
57+
examples.NewExample(
58+
`List PostgreSQL Flex available versions`,
59+
"$ stackit postgresflex options --versions"),
60+
examples.NewExample(
61+
`List PostgreSQL Flex storage options for a given flavor. The flavor ID can be retrieved by running "$ stackit postgresflex options --flavors"`,
62+
"$ stackit postgresflex options --storages --flavor-id <FLAVOR_ID>"),
63+
),
64+
RunE: func(cmd *cobra.Command, args []string) error {
65+
ctx := context.Background()
66+
model, err := parseInput(cmd)
67+
if err != nil {
68+
return err
69+
}
70+
71+
// Configure API client
72+
apiClient, err := client.ConfigureClient(cmd)
73+
if err != nil {
74+
return err
75+
}
76+
77+
// Call API
78+
err = buildAndExecuteRequest(ctx, cmd, model, apiClient)
79+
if err != nil {
80+
return fmt.Errorf("get PostgreSQL Flex options: %w", err)
81+
}
82+
83+
return nil
84+
},
85+
}
86+
configureFlags(cmd)
87+
return cmd
88+
}
89+
90+
func configureFlags(cmd *cobra.Command) {
91+
cmd.Flags().Bool(flavorsFlag, false, "Lists supported flavors")
92+
cmd.Flags().Bool(versionsFlag, false, "Lists supported versions")
93+
cmd.Flags().Bool(storagesFlag, false, "Lists supported storages for a given flavor")
94+
cmd.Flags().String(flavorIdFlag, "", `The flavor ID to show storages for. Only relevant when "--storages" is passed`)
95+
}
96+
97+
func parseInput(cmd *cobra.Command) (*inputModel, error) {
98+
globalFlags := globalflags.Parse(cmd)
99+
flavors := flags.FlagToBoolValue(cmd, flavorsFlag)
100+
versions := flags.FlagToBoolValue(cmd, versionsFlag)
101+
storages := flags.FlagToBoolValue(cmd, storagesFlag)
102+
flavorId := flags.FlagToStringPointer(cmd, flavorIdFlag)
103+
104+
if !flavors && !versions && !storages {
105+
return nil, fmt.Errorf("%s\n\n%s",
106+
"please specify at least one category for which to list the available options.",
107+
"Get details on the available flags by re-running your command with the --help flag.")
108+
}
109+
110+
if storages && flavorId == nil {
111+
return nil, fmt.Errorf("%s\n\n%s\n%s",
112+
`please specify a flavor ID to show storages for by setting the flag "--flavor-id <FLAVOR_ID>".`,
113+
"You can get the available flavor IDs by running:",
114+
" $ stackit postgresflex options --flavors")
115+
}
116+
117+
return &inputModel{
118+
GlobalFlagModel: globalFlags,
119+
Flavors: flavors,
120+
Versions: versions,
121+
Storages: storages,
122+
FlavorId: flags.FlagToStringPointer(cmd, flavorIdFlag),
123+
}, nil
124+
}
125+
126+
type postgresFlexOptionsClient interface {
127+
ListFlavorsExecute(ctx context.Context, projectId string) (*postgresflex.ListFlavorsResponse, error)
128+
ListVersionsExecute(ctx context.Context, projectId string) (*postgresflex.ListVersionsResponse, error)
129+
ListStoragesExecute(ctx context.Context, projectId, flavorId string) (*postgresflex.ListStoragesResponse, error)
130+
}
131+
132+
func buildAndExecuteRequest(ctx context.Context, cmd *cobra.Command, model *inputModel, apiClient postgresFlexOptionsClient) error {
133+
var flavors *postgresflex.ListFlavorsResponse
134+
var versions *postgresflex.ListVersionsResponse
135+
var storages *postgresflex.ListStoragesResponse
136+
var err error
137+
138+
if model.Flavors {
139+
flavors, err = apiClient.ListFlavorsExecute(ctx, model.ProjectId)
140+
if err != nil {
141+
return fmt.Errorf("get PostgreSQL Flex flavors: %w", err)
142+
}
143+
}
144+
if model.Versions {
145+
versions, err = apiClient.ListVersionsExecute(ctx, model.ProjectId)
146+
if err != nil {
147+
return fmt.Errorf("get PostgreSQL Flex versions: %w", err)
148+
}
149+
}
150+
if model.Storages {
151+
storages, err = apiClient.ListStoragesExecute(ctx, model.ProjectId, *model.FlavorId)
152+
if err != nil {
153+
return fmt.Errorf("get PostgreSQL Flex storages: %w", err)
154+
}
155+
}
156+
157+
return outputResult(cmd, model, flavors, versions, storages)
158+
}
159+
160+
func outputResult(cmd *cobra.Command, model *inputModel, flavors *postgresflex.ListFlavorsResponse, versions *postgresflex.ListVersionsResponse, storages *postgresflex.ListStoragesResponse) error {
161+
options := &options{}
162+
if flavors != nil {
163+
options.Flavors = flavors.Flavors
164+
}
165+
if versions != nil {
166+
options.Versions = versions.Versions
167+
}
168+
if storages != nil && model.FlavorId != nil {
169+
options.Storages = &flavorStorages{
170+
FlavorId: *model.FlavorId,
171+
Storages: storages,
172+
}
173+
}
174+
175+
switch model.OutputFormat {
176+
case globalflags.JSONOutputFormat:
177+
details, err := json.MarshalIndent(options, "", " ")
178+
if err != nil {
179+
return fmt.Errorf("marshal PostgreSQL Flex options: %w", err)
180+
}
181+
cmd.Println(string(details))
182+
return nil
183+
default:
184+
return outputResultAsTable(cmd, model, options)
185+
}
186+
}
187+
188+
func outputResultAsTable(cmd *cobra.Command, model *inputModel, options *options) error {
189+
content := ""
190+
if model.Flavors {
191+
content += renderFlavors(*options.Flavors)
192+
}
193+
if model.Versions {
194+
content += renderVersions(*options.Versions)
195+
}
196+
if model.Storages {
197+
content += renderStorages(options.Storages.Storages)
198+
}
199+
200+
err := pager.Display(cmd, content)
201+
if err != nil {
202+
return fmt.Errorf("display output: %w", err)
203+
}
204+
205+
return nil
206+
}
207+
208+
func renderFlavors(flavors []postgresflex.Flavor) string {
209+
if len(flavors) == 0 {
210+
return ""
211+
}
212+
213+
table := tables.NewTable()
214+
table.SetHeader("ID", "CPU", "MEMORY", "DESCRIPTION")
215+
for i := range flavors {
216+
f := flavors[i]
217+
table.AddRow(*f.Id, *f.Cpu, *f.Memory, *f.Description)
218+
}
219+
return table.Render()
220+
}
221+
222+
func renderVersions(versions []string) string {
223+
if len(versions) == 0 {
224+
return ""
225+
}
226+
227+
table := tables.NewTable()
228+
table.SetHeader("VERSION")
229+
for i := range versions {
230+
v := versions[i]
231+
table.AddRow(v)
232+
}
233+
return table.Render()
234+
}
235+
236+
func renderStorages(resp *postgresflex.ListStoragesResponse) string {
237+
if resp.StorageClasses == nil || len(*resp.StorageClasses) == 0 {
238+
return ""
239+
}
240+
storageClasses := *resp.StorageClasses
241+
242+
table := tables.NewTable()
243+
table.SetHeader("MIN STORAGE", "MAX STORAGE", "STORAGE CLASS")
244+
for i := range storageClasses {
245+
sc := storageClasses[i]
246+
table.AddRow(*resp.StorageRange.Min, *resp.StorageRange.Max, sc)
247+
}
248+
table.EnableAutoMergeOnColumns(1, 2, 3)
249+
return table.Render()
250+
}

0 commit comments

Comments
 (0)