Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoInstallSign in
Socket

mygithub.libinneed.workers.dev/stackitcloud/stackit-cli

Package Overview
Dependencies
Versions
173
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mygithub.libinneed.workers.dev/stackitcloud/stackit-cli - npm Package Compare versions

Comparing version
v0.36.0
to
v0.37.0-test1
+44
docs/stackit_git_flavor_list.md
## stackit git flavor list
Lists instances flavors of STACKIT Git.
### Synopsis
Lists instances flavors of STACKIT Git for the current project.
```
stackit git flavor list [flags]
```
### Examples
```
List STACKIT Git flavors
$ stackit git flavor list
Lists up to 10 STACKIT Git flavors
$ stackit git flavor list --limit=10
```
### Options
```
-h, --help Help for "stackit git flavor list"
--limit int Limit the output to the first n elements
```
### 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 git flavor](./stackit_git_flavor.md) - Provides functionality for STACKIT Git flavors
## stackit git flavor
Provides functionality for STACKIT Git flavors
### Synopsis
Provides functionality for STACKIT Git flavors.
```
stackit git flavor [flags]
```
### Options
```
-h, --help Help for "stackit git flavor"
```
### 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 git](./stackit_git.md) - Provides functionality for STACKIT Git
* [stackit git flavor list](./stackit_git_flavor_list.md) - Lists instances flavors of STACKIT Git.
## stackit git instance create
Creates STACKIT Git instance
### Synopsis
Create a STACKIT Git instance by name.
```
stackit git instance create [flags]
```
### Examples
```
Create a instance with name 'my-new-instance'
$ stackit git instance create --name my-new-instance
Create a instance with name 'my-new-instance' and flavor
$ stackit git instance create --name my-new-instance --flavor git-100'
Create a instance with name 'my-new-instance' and acl
$ stackit git instance create --name my-new-instance --acl 1.1.1.1/1'
```
### Options
```
--acl strings Acl for the instance.
--flavor string Flavor of the instance.
-h, --help Help for "stackit git instance create"
--name string The name of the instance.
```
### 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 git instance](./stackit_git_instance.md) - Provides functionality for STACKIT Git instances
## stackit git instance delete
Deletes STACKIT Git instance
### Synopsis
Deletes a STACKIT Git instance by its internal ID.
```
stackit git instance delete INSTANCE_ID [flags]
```
### Examples
```
Delete a instance with ID "xxx"
$ stackit git instance delete xxx
```
### Options
```
-h, --help Help for "stackit git instance delete"
```
### 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 git instance](./stackit_git_instance.md) - Provides functionality for STACKIT Git instances
## stackit git instance describe
Describes STACKIT Git instance
### Synopsis
Describes a STACKIT Git instance by its internal ID.
```
stackit git instance describe INSTANCE_ID [flags]
```
### Examples
```
Describe instance "xxx"
$ stackit git describe xxx
```
### Options
```
-h, --help Help for "stackit git instance describe"
```
### 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 git instance](./stackit_git_instance.md) - Provides functionality for STACKIT Git instances
## stackit git instance list
Lists all instances of STACKIT Git.
### Synopsis
Lists all instances of STACKIT Git for the current project.
```
stackit git instance list [flags]
```
### Examples
```
List all STACKIT Git instances
$ stackit git instance list
Lists up to 10 STACKIT Git instances
$ stackit git instance list --limit=10
```
### Options
```
-h, --help Help for "stackit git instance list"
--limit int Limit the output to the first n elements
```
### 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 git instance](./stackit_git_instance.md) - Provides functionality for STACKIT Git instances
## stackit git instance
Provides functionality for STACKIT Git instances
### Synopsis
Provides functionality for STACKIT Git instances.
```
stackit git instance [flags]
```
### Options
```
-h, --help Help for "stackit git instance"
```
### 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 git](./stackit_git.md) - Provides functionality for STACKIT Git
* [stackit git instance create](./stackit_git_instance_create.md) - Creates STACKIT Git instance
* [stackit git instance delete](./stackit_git_instance_delete.md) - Deletes STACKIT Git instance
* [stackit git instance describe](./stackit_git_instance_describe.md) - Describes STACKIT Git instance
* [stackit git instance list](./stackit_git_instance_list.md) - Lists all instances of STACKIT Git.
## stackit ske cluster hibernate
Trigger hibernate for a SKE cluster
### Synopsis
Trigger hibernate for a STACKIT Kubernetes Engine (SKE) cluster.
```
stackit ske cluster hibernate CLUSTER_NAME [flags]
```
### Examples
```
Trigger hibernate for a SKE cluster with name "my-cluster"
$ stackit ske cluster hibernate my-cluster
```
### Options
```
-h, --help Help for "stackit ske cluster hibernate"
```
### 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 ske cluster](./stackit_ske_cluster.md) - Provides functionality for SKE cluster
## stackit ske cluster maintenance
Trigger maintenance for a SKE cluster
### Synopsis
Trigger maintenance for a STACKIT Kubernetes Engine (SKE) cluster.
```
stackit ske cluster maintenance CLUSTER_NAME [flags]
```
### Examples
```
Trigger maintenance for a SKE cluster with name "my-cluster"
$ stackit ske cluster maintenance my-cluster
```
### Options
```
-h, --help Help for "stackit ske cluster maintenance"
```
### 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 ske cluster](./stackit_ske_cluster.md) - Provides functionality for SKE cluster
## stackit ske cluster reconcile
Trigger reconcile for a SKE cluster
### Synopsis
Trigger reconcile for a STACKIT Kubernetes Engine (SKE) cluster.
```
stackit ske cluster reconcile CLUSTER_NAME [flags]
```
### Examples
```
Trigger reconcile for a SKE cluster with name "my-cluster"
$ stackit ske cluster reconcile my-cluster
```
### Options
```
-h, --help Help for "stackit ske cluster reconcile"
```
### 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 ske cluster](./stackit_ske_cluster.md) - Provides functionality for SKE cluster
## stackit ske cluster wakeup
Trigger wakeup from hibernation for a SKE cluster
### Synopsis
Trigger wakeup from hibernation for a STACKIT Kubernetes Engine (SKE) cluster.
```
stackit ske cluster wakeup CLUSTER_NAME [flags]
```
### Examples
```
Trigger wakeup from hibernation for a SKE cluster with name "my-cluster"
$ stackit ske cluster wakeup my-cluster
```
### Options
```
-h, --help Help for "stackit ske cluster wakeup"
```
### 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 ske cluster](./stackit_ske_cluster.md) - Provides functionality for SKE cluster
package flavor
import (
"github.com/stackitcloud/stackit-cli/internal/cmd/git/flavor/list"
"github.com/stackitcloud/stackit-cli/internal/cmd/params"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/spf13/cobra"
)
func NewCmd(params *params.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: "flavor",
Short: "Provides functionality for STACKIT Git flavors",
Long: "Provides functionality for STACKIT Git flavors.",
Args: args.NoArgs,
Run: utils.CmdHelp,
}
addSubcommands(cmd, params)
return cmd
}
func addSubcommands(cmd *cobra.Command, params *params.CmdParams) {
cmd.AddCommand(
list.NewCmd(params),
)
}
package list
import (
"context"
"strconv"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/stackitcloud/stackit-cli/internal/cmd/params"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/git"
)
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &git.APIClient{}
var testProjectId = uuid.NewString()
const (
testLimit = 10
)
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
globalflags.ProjectIdFlag: testProjectId,
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
ProjectId: testProjectId,
Verbosity: globalflags.VerbosityDefault,
},
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureRequest(mods ...func(request *git.ApiListFlavorsRequest)) git.ApiListFlavorsRequest {
request := testClient.ListFlavors(testCtx, testProjectId)
for _, mod := range mods {
mod(&request)
}
return request
}
func TestParseInput(t *testing.T) {
tests := []struct {
description string
flagValues map[string]string
isValid bool
expectedModel *inputModel
}{
{
description: "base",
flagValues: fixtureFlagValues(),
isValid: true,
expectedModel: fixtureInputModel(),
},
{
description: "no values",
flagValues: map[string]string{},
isValid: false,
},
{
description: "project id missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, globalflags.ProjectIdFlag)
}),
isValid: false,
},
{
description: "project id invalid 1",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[globalflags.ProjectIdFlag] = ""
}),
isValid: false,
},
{
description: "project id invalid 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[globalflags.ProjectIdFlag] = "invalid-uuid"
}),
isValid: false,
},
{
description: "with limit flag",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues["limit"] = strconv.Itoa(testLimit)
}),
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.Limit = utils.Ptr(int64(testLimit))
}),
},
{
description: "with limit flag == 0",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues["limit"] = strconv.Itoa(0)
}),
isValid: false,
},
{
description: "with limit flag < 0",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues["limit"] = strconv.Itoa(-1)
}),
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
p := print.NewPrinter()
cmd := NewCmd(&params.CmdParams{Printer: p})
err := globalflags.Configure(cmd.Flags())
if err != nil {
t.Fatalf("configure global flags: %v", err)
}
for flag, value := range tt.flagValues {
err := cmd.Flags().Set(flag, value)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("setting flag --%s=%s: %v", flag, value, err)
}
}
err = cmd.ValidateRequiredFlags()
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error validating flags: %v", err)
}
model, err := parseInput(p, cmd)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error parsing flags: %v", err)
}
if !tt.isValid {
t.Fatalf("did not fail on invalid input")
}
diff := cmp.Diff(model, tt.expectedModel)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest git.ApiListFlavorsRequest
}{
{
description: "base",
model: fixtureInputModel(),
expectedRequest: fixtureRequest(),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
request := buildRequest(testCtx, tt.model, testClient)
diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmpopts.EquateComparable(testCtx),
)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
func TestOutputResult(t *testing.T) {
type args struct {
outputFormat string
flavors []git.Flavor
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "empty",
args: args{},
wantErr: false,
},
{
name: "set empty flavors slice",
args: args{
flavors: []git.Flavor{},
},
wantErr: false,
},
{
name: "set empty flavors in flavors slice",
args: args{
flavors: []git.Flavor{{}},
},
wantErr: false,
},
}
p := print.NewPrinter()
p.Cmd = NewCmd(&params.CmdParams{Printer: p})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := outputResult(p, tt.args.outputFormat, tt.args.flavors); (err != nil) != tt.wantErr {
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
package list
import (
"context"
"encoding/json"
"fmt"
"github.com/goccy/go-yaml"
"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/projectname"
"github.com/stackitcloud/stackit-cli/internal/pkg/services/git/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/git"
)
type inputModel struct {
*globalflags.GlobalFlagModel
Limit *int64
}
const limitFlag = "limit"
func NewCmd(params *params.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: "Lists instances flavors of STACKIT Git.",
Long: "Lists instances flavors of STACKIT Git for the current project.",
Args: args.NoArgs,
Example: examples.Build(
examples.NewExample(
`List STACKIT Git flavors`,
"$ stackit git flavor list"),
examples.NewExample(
"Lists up to 10 STACKIT Git flavors",
"$ stackit git flavor list --limit=10",
),
),
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := context.Background()
model, err := parseInput(params.Printer, cmd)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(params.Printer)
if err != nil {
return err
}
// Call API
req := buildRequest(ctx, model, apiClient)
resp, err := req.Execute()
if err != nil {
return fmt.Errorf("get STACKIT Git flavors: %w", err)
}
flavors := *resp.Flavors
if len(flavors) == 0 {
projectLabel, err := projectname.GetProjectName(ctx, params.Printer, params.CliVersion, cmd)
if err != nil {
params.Printer.Debug(print.ErrorLevel, "get project name: %v", err)
projectLabel = model.ProjectId
}
params.Printer.Info("No flavors found for project %q\n", projectLabel)
return nil
} else if model.Limit != nil && len(flavors) > int(*model.Limit) {
flavors = (flavors)[:*model.Limit]
}
return outputResult(params.Printer, model.OutputFormat, flavors)
},
}
configureFlags(cmd)
return cmd
}
func configureFlags(cmd *cobra.Command) {
cmd.Flags().Int64(limitFlag, 0, "Limit the output to the first n elements")
}
func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)
if globalFlags.ProjectId == "" {
return nil, &errors.ProjectIdError{}
}
limit := flags.FlagToInt64Pointer(p, cmd, limitFlag)
if limit != nil && *limit < 1 {
return nil, &errors.FlagValidationError{
Flag: limitFlag,
Details: "must be greater than 0",
}
}
model := inputModel{
GlobalFlagModel: globalFlags,
Limit: limit,
}
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 *git.APIClient) git.ApiListFlavorsRequest {
return apiClient.ListFlavors(ctx, model.ProjectId)
}
func outputResult(p *print.Printer, outputFormat string, flavors []git.Flavor) error {
switch outputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(flavors, "", " ")
if err != nil {
return fmt.Errorf("marshal Observability flavor list: %w", err)
}
p.Outputln(string(details))
return nil
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(flavors, yaml.IndentSequence(true), yaml.UseJSONMarshaler())
if err != nil {
return fmt.Errorf("marshal Observability flavor list: %w", err)
}
p.Outputln(string(details))
return nil
default:
table := tables.NewTable()
table.SetHeader("ID", "DESCRIPTION", "DISPLAY_NAME", "AVAILABLE", "SKU")
for i := range flavors {
flavor := (flavors)[i]
table.AddRow(
utils.PtrString(flavor.Id),
utils.PtrString(flavor.Description),
utils.PtrString(flavor.DisplayName),
utils.PtrString(flavor.Availability),
utils.PtrString(flavor.Sku),
)
}
err := table.Display(p)
if err != nil {
return fmt.Errorf("render table: %w", err)
}
return nil
}
}
package create
import (
"context"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/stackitcloud/stackit-cli/internal/cmd/params"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/git"
)
type testCtxKey struct{}
var (
testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
testClient = &git.APIClient{}
testProjectId = uuid.NewString()
testName = "test-instance"
testFlavor = "git-100"
testAcl = []string{"0.0.0.0/0"}
)
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
globalflags.ProjectIdFlag: testProjectId,
nameFlag: testName,
flavorFlag: testFlavor,
aclFlag: testAcl[0],
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{ProjectId: testProjectId, Verbosity: globalflags.VerbosityDefault},
Name: testName,
Flavor: testFlavor,
Acl: testAcl,
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureCreatePayload(mods ...func(payload *git.CreateInstancePayload)) (payload git.CreateInstancePayload) {
payload = git.CreateInstancePayload{
Name: &testName,
Flavor: git.CreateInstancePayloadGetFlavorAttributeType(&testFlavor),
Acl: &testAcl,
}
for _, mod := range mods {
mod(&payload)
}
return payload
}
func fixtureRequest(mods ...func(request *git.ApiCreateInstanceRequest)) git.ApiCreateInstanceRequest {
request := testClient.CreateInstance(testCtx, testProjectId)
request = request.CreateInstancePayload(fixtureCreatePayload())
for _, mod := range mods {
mod(&request)
}
return request
}
func TestParseInput(t *testing.T) {
tests := []struct {
description string
flagValues map[string]string
isValid bool
expectedModel *inputModel
}{
{
description: "base",
flagValues: fixtureFlagValues(),
isValid: true,
expectedModel: fixtureInputModel(),
},
{
description: "no values",
flagValues: map[string]string{},
isValid: false,
},
{
description: "project id missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, globalflags.ProjectIdFlag)
}),
isValid: false,
},
{
description: "project id invalid 1",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[globalflags.ProjectIdFlag] = ""
}),
isValid: false,
},
{
description: "project id invalid 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[globalflags.ProjectIdFlag] = "invalid-uuid"
}),
isValid: false,
},
{
description: "name missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, nameFlag)
}),
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
p := print.NewPrinter()
cmd := NewCmd(&params.CmdParams{Printer: p})
if err := globalflags.Configure(cmd.Flags()); err != nil {
t.Errorf("cannot configure global flags: %v", err)
}
for flag, value := range tt.flagValues {
err := cmd.Flags().Set(flag, value)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("setting flag --%s=%s: %v", flag, value, err)
}
}
if err := cmd.ValidateFlagGroups(); err != nil {
if !tt.isValid {
return
}
t.Fatalf("error validating flag groups: %v", err)
}
if err := cmd.ValidateRequiredFlags(); err != nil {
if !tt.isValid {
return
}
t.Fatalf("error validating flags: %v", err)
}
model, err := parseInput(p, cmd)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error parsing flags: %v", err)
}
if !tt.isValid {
t.Fatalf("did not fail on invalid input")
}
diff := cmp.Diff(model, tt.expectedModel)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest git.ApiCreateInstanceRequest
}{
{
description: "base",
model: fixtureInputModel(),
expectedRequest: fixtureRequest(),
},
{
description: "name flag",
model: fixtureInputModel(func(model *inputModel) {
model.Name = "new-name"
}),
expectedRequest: fixtureRequest(func(request *git.ApiCreateInstanceRequest) {
*request = (*request).CreateInstancePayload(fixtureCreatePayload(func(payload *git.CreateInstancePayload) {
payload.Name = utils.Ptr("new-name")
}))
}),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
request := buildRequest(testCtx, tt.model, testClient)
diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmpopts.EquateComparable(testCtx),
cmp.AllowUnexported(git.NullableString{}),
)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
func TestOutputResult(t *testing.T) {
type args struct {
model *inputModel
resp *git.Instance
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "nil",
args: args{
model: nil,
resp: nil,
},
wantErr: true,
},
{
name: "empty input",
args: args{
model: &inputModel{},
resp: &git.Instance{},
},
wantErr: false,
},
{
name: "output json",
args: args{
model: &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
OutputFormat: print.JSONOutputFormat,
},
},
resp: nil,
},
wantErr: false,
},
}
p := print.NewPrinter()
p.Cmd = NewCmd(&params.CmdParams{Printer: p})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := outputResult(p, tt.args.model, tt.args.resp); (err != nil) != tt.wantErr {
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
package create
import (
"context"
"encoding/json"
"fmt"
"github.com/goccy/go-yaml"
"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/git/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/spinner"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/git"
"github.com/stackitcloud/stackit-sdk-go/services/git/wait"
)
const (
nameFlag = "name"
flavorFlag = "flavor"
aclFlag = "acl"
)
type inputModel struct {
*globalflags.GlobalFlagModel
Id *string
Name string
Flavor string
Acl []string
}
func NewCmd(params *params.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: "create",
Short: "Creates STACKIT Git instance",
Long: "Create a STACKIT Git instance by name.",
Args: args.NoArgs,
Example: examples.Build(
examples.NewExample(
`Create a instance with name 'my-new-instance'`,
`$ stackit git instance create --name my-new-instance`,
),
examples.NewExample(
`Create a instance with name 'my-new-instance' and flavor`,
`$ stackit git instance create --name my-new-instance --flavor git-100'`,
),
examples.NewExample(
`Create a instance with name 'my-new-instance' and acl`,
`$ stackit git instance create --name my-new-instance --acl 1.1.1.1/1'`,
),
),
RunE: func(cmd *cobra.Command, _ []string) (err error) {
ctx := context.Background()
model, err := parseInput(params.Printer, cmd)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(params.Printer)
if err != nil {
return err
}
if !model.AssumeYes {
prompt := fmt.Sprintf("Are you sure you want to create the instance %q?", model.Name)
err = params.Printer.PromptForConfirmation(prompt)
if err != nil {
return err
}
}
// Call API
request := buildRequest(ctx, model, apiClient)
result, err := request.Execute()
if err != nil {
return fmt.Errorf("create stackit git instance: %w", err)
}
model.Id = result.Id
// Wait for async operation, if async mode not enabled
if !model.Async {
s := spinner.New(params.Printer)
s.Start("Creating stackit git instance")
_, err = wait.CreateGitInstanceWaitHandler(ctx, apiClient, model.ProjectId, *model.Id).WaitWithContext(ctx)
if err != nil {
return fmt.Errorf("wait for stackit git Instance creation: %w", err)
}
s.Stop()
}
return outputResult(params.Printer, model, result)
},
}
configureFlags(cmd)
return cmd
}
func configureFlags(cmd *cobra.Command) {
cmd.Flags().String(nameFlag, "", "The name of the instance.")
cmd.Flags().String(flavorFlag, "", "Flavor of the instance.")
cmd.Flags().StringSlice(aclFlag, []string{}, "Acl for the instance.")
if err := flags.MarkFlagsRequired(cmd, nameFlag); err != nil {
cobra.CheckErr(err)
}
}
func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)
if globalFlags.ProjectId == "" {
return nil, &errors.ProjectIdError{}
}
name := flags.FlagToStringValue(p, cmd, nameFlag)
flavor := flags.FlagToStringValue(p, cmd, flavorFlag)
acl := flags.FlagToStringSliceValue(p, cmd, aclFlag)
model := inputModel{
GlobalFlagModel: globalFlags,
Name: name,
Flavor: flavor,
Acl: acl,
}
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 *git.APIClient) git.ApiCreateInstanceRequest {
return apiClient.CreateInstance(ctx, model.ProjectId).CreateInstancePayload(createPayload(model))
}
func createPayload(model *inputModel) git.CreateInstancePayload {
return git.CreateInstancePayload{
Name: &model.Name,
Flavor: git.CreateInstancePayloadGetFlavorAttributeType(&model.Flavor),
Acl: &model.Acl,
}
}
func outputResult(p *print.Printer, model *inputModel, resp *git.Instance) error {
if model == nil {
return fmt.Errorf("input model is nil")
}
var outputFormat string
if model.GlobalFlagModel != nil {
outputFormat = model.OutputFormat
}
switch outputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(resp, "", " ")
if err != nil {
return fmt.Errorf("marshal instance: %w", err)
}
p.Outputln(string(details))
return nil
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true), yaml.UseJSONMarshaler())
if err != nil {
return fmt.Errorf("marshal iminstanceage: %w", err)
}
p.Outputln(string(details))
return nil
default:
p.Outputf("Created instance %q with id %s\n", model.Name, utils.PtrString(model.Id))
return nil
}
}
package delete
import (
"context"
"testing"
"github.com/stackitcloud/stackit-cli/internal/cmd/params"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/stackitcloud/stackit-sdk-go/services/git"
)
type testCtxKey struct{}
var (
testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
testClient = &git.APIClient{}
testProjectId = uuid.NewString()
testInstanceId = uuid.NewString()
)
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
globalflags.ProjectIdFlag: testProjectId,
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{ProjectId: testProjectId, Verbosity: globalflags.VerbosityDefault},
InstanceId: testInstanceId,
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureRequest(mods ...func(request *git.ApiDeleteInstanceRequest)) git.ApiDeleteInstanceRequest {
request := testClient.DeleteInstance(testCtx, testProjectId, testInstanceId)
for _, mod := range mods {
mod(&request)
}
return request
}
func TestParseInput(t *testing.T) {
tests := []struct {
description string
flagValues map[string]string
args []string
isValid bool
expectedModel *inputModel
}{
{
description: "base",
flagValues: fixtureFlagValues(),
args: []string{testInstanceId},
isValid: true,
expectedModel: fixtureInputModel(),
},
{
description: "project id invalid 1",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[globalflags.ProjectIdFlag] = ""
}),
isValid: false,
},
{
description: "project id invalid 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[globalflags.ProjectIdFlag] = "invalid-uuid"
}),
isValid: false,
},
{
description: "no arguments",
flagValues: fixtureFlagValues(),
args: nil,
isValid: false,
},
{
description: "multiple arguments",
flagValues: fixtureFlagValues(),
args: []string{"foo", "bar"},
isValid: false,
},
{
description: "invalid instance id",
flagValues: fixtureFlagValues(),
args: []string{"foo"},
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
p := print.NewPrinter()
cmd := NewCmd(&params.CmdParams{Printer: p})
err := globalflags.Configure(cmd.Flags())
if err != nil {
t.Fatalf("configure global flags: %v", err)
}
cmd.SetArgs(tt.args)
for flag, value := range tt.flagValues {
err := cmd.Flags().Set(flag, value)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("setting flag --%s=%s: %v", flag, value, err)
}
}
if err := cmd.ValidateArgs(tt.args); err != nil {
if !tt.isValid {
return
}
}
err = cmd.ValidateRequiredFlags()
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error validating flags: %v", err)
}
model, err := parseInput(p, cmd, tt.args)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error parsing flags: %v", err)
}
if !tt.isValid {
t.Fatalf("did not fail on invalid input")
}
diff := cmp.Diff(model, tt.expectedModel)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest git.ApiDeleteInstanceRequest
}{
{
description: "base",
model: fixtureInputModel(),
expectedRequest: fixtureRequest(),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
request := buildRequest(testCtx, tt.model, testClient)
diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmpopts.EquateComparable(testCtx),
)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
package delete
import (
"context"
"fmt"
"github.com/stackitcloud/stackit-cli/internal/cmd/params"
"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/git/client"
gitUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/git/utils"
"github.com/stackitcloud/stackit-cli/internal/pkg/spinner"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/git"
"github.com/stackitcloud/stackit-sdk-go/services/git/wait"
)
type inputModel struct {
*globalflags.GlobalFlagModel
InstanceId string
}
const instanceIdArg = "INSTANCE_ID"
func NewCmd(params *params.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: fmt.Sprintf("delete %s", instanceIdArg),
Short: "Deletes STACKIT Git instance",
Long: "Deletes a STACKIT Git instance by its internal ID.",
Args: args.SingleArg(instanceIdArg, utils.ValidateUUID),
Example: examples.Build(
examples.NewExample(`Delete a instance with ID "xxx"`,
`$ stackit git instance delete xxx`),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(params.Printer, cmd, args)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(params.Printer)
if err != nil {
return err
}
projectName, err := projectname.GetProjectName(ctx, params.Printer, params.CliVersion, cmd)
if err != nil {
params.Printer.Debug(print.ErrorLevel, "get project name: %v", err)
projectName = model.ProjectId
}
instanceName, err := gitUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId)
if err != nil {
params.Printer.Debug(print.ErrorLevel, "get stackit git intance name: %v", err)
instanceName = model.InstanceId
} else if instanceName == "" {
instanceName = model.InstanceId
}
if !model.AssumeYes {
prompt := fmt.Sprintf("Are you sure you want to delete the stackit git instance %q for %q?", instanceName, projectName)
err = params.Printer.PromptForConfirmation(prompt)
if err != nil {
return err
}
}
// Call API
request := buildRequest(ctx, model, apiClient)
err = request.Execute()
if err != nil {
return fmt.Errorf("delete instance: %w", err)
}
// Wait for async operation, if async mode not enabled
if !model.Async {
s := spinner.New(params.Printer)
s.Start("Deleting stackit git instance")
_, err = wait.DeleteGitInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId).WaitWithContext(ctx)
if err != nil {
return fmt.Errorf("wait for stackit git instance deletion: %w", err)
}
s.Stop()
}
operationState := "Deleted"
if model.Async {
operationState = "Triggered deletion of"
}
params.Printer.Info("%s stackit git instance %s \n", operationState, model.InstanceId)
return nil
},
}
return cmd
}
func parseInput(p *print.Printer, cmd *cobra.Command, cliArgs []string) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)
if globalFlags.ProjectId == "" {
return nil, &errors.ProjectIdError{}
}
model := inputModel{
GlobalFlagModel: globalFlags,
InstanceId: cliArgs[0],
}
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 *git.APIClient) git.ApiDeleteInstanceRequest {
return apiClient.DeleteInstance(ctx, model.ProjectId, model.InstanceId)
}
package describe
import (
"context"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/stackitcloud/stackit-cli/internal/cmd/params"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-sdk-go/services/git"
)
type testCtxKey struct{}
var (
testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
testClient = &git.APIClient{}
testProjectId = uuid.NewString()
testInstanceId = []string{uuid.NewString()}
)
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
globalflags.ProjectIdFlag: testProjectId,
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{ProjectId: testProjectId, Verbosity: globalflags.VerbosityDefault},
InstanceId: testInstanceId[0],
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureRequest(mods ...func(request *git.ApiGetInstanceRequest)) git.ApiGetInstanceRequest {
request := testClient.GetInstance(testCtx, testProjectId, testInstanceId[0])
for _, mod := range mods {
mod(&request)
}
return request
}
func TestParseInput(t *testing.T) {
tests := []struct {
description string
flagValues map[string]string
isValid bool
args []string
expectedModel *inputModel
}{
{
description: "base",
flagValues: fixtureFlagValues(),
expectedModel: fixtureInputModel(),
args: testInstanceId,
isValid: true,
},
{
description: "no values",
flagValues: map[string]string{},
args: testInstanceId,
isValid: false,
},
{
description: "project id missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, globalflags.ProjectIdFlag)
}),
args: testInstanceId,
isValid: false,
},
{
description: "project id invalid 1",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[globalflags.ProjectIdFlag] = ""
}),
args: testInstanceId,
isValid: false,
},
{
description: "project id invalid 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[globalflags.ProjectIdFlag] = "invalid-uuid"
}),
args: testInstanceId,
isValid: false,
},
{
description: "no instance id passed",
flagValues: fixtureFlagValues(),
args: nil,
isValid: false,
},
{
description: "multiple instance ids passed",
flagValues: fixtureFlagValues(),
args: []string{uuid.NewString(), uuid.NewString()},
isValid: false,
},
{
description: "invalid instance id passed",
flagValues: fixtureFlagValues(),
args: []string{"foobar"},
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
p := print.NewPrinter()
cmd := NewCmd(&params.CmdParams{Printer: p})
if err := globalflags.Configure(cmd.Flags()); err != nil {
t.Errorf("cannot configure global flags: %v", err)
}
for flag, value := range tt.flagValues {
err := cmd.Flags().Set(flag, value)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("setting flag --%s=%s: %v", flag, value, err)
}
}
if err := cmd.ValidateRequiredFlags(); err != nil {
if !tt.isValid {
return
}
t.Fatalf("error validating flags: %v", err)
}
if err := cmd.ValidateArgs(tt.args); err != nil {
if !tt.isValid {
return
}
}
model, err := parseInput(p, cmd, tt.args)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error parsing flags: %v", err)
}
if !tt.isValid {
t.Fatalf("did not fail on invalid input")
}
diff := cmp.Diff(model, tt.expectedModel)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest git.ApiGetInstanceRequest
}{
{
description: "base",
model: fixtureInputModel(),
expectedRequest: fixtureRequest(),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
request := buildRequest(testCtx, tt.model, testClient)
diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmpopts.EquateComparable(testCtx),
)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
func TestOutputResult(t *testing.T) {
type args struct {
outputFormat string
resp *git.Instance
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "empty",
args: args{
resp: &git.Instance{},
},
wantErr: false,
},
{
name: "nil",
args: args{},
wantErr: true,
},
}
p := print.NewPrinter()
p.Cmd = NewCmd(&params.CmdParams{Printer: p})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := outputResult(p, tt.args.outputFormat, tt.args.resp); (err != nil) != tt.wantErr {
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
package describe
import (
"context"
"encoding/json"
"fmt"
"github.com/stackitcloud/stackit-cli/internal/cmd/params"
"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/services/git/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/git"
)
type inputModel struct {
*globalflags.GlobalFlagModel
InstanceId string
}
const instanceIdArg = "INSTANCE_ID"
func NewCmd(params *params.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: fmt.Sprintf("describe %s", instanceIdArg),
Short: "Describes STACKIT Git instance",
Long: "Describes a STACKIT Git instance by its internal ID.",
Args: args.SingleArg(instanceIdArg, utils.ValidateUUID),
Example: examples.Build(
examples.NewExample(`Describe instance "xxx"`, `$ stackit git describe xxx`),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(params.Printer, cmd, args)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(params.Printer)
if err != nil {
return err
}
// Call API
request := buildRequest(ctx, model, apiClient)
instance, err := request.Execute()
if err != nil {
return fmt.Errorf("get instance: %w", err)
}
if err := outputResult(params.Printer, model.OutputFormat, instance); err != nil {
return err
}
return nil
},
}
return cmd
}
func parseInput(p *print.Printer, cmd *cobra.Command, cliArgs []string) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)
if globalFlags.ProjectId == "" {
return nil, &errors.ProjectIdError{}
}
model := inputModel{
GlobalFlagModel: globalFlags,
InstanceId: cliArgs[0],
}
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 *git.APIClient) git.ApiGetInstanceRequest {
return apiClient.GetInstance(ctx, model.ProjectId, model.InstanceId)
}
func outputResult(p *print.Printer, outputFormat string, resp *git.Instance) error {
if resp == nil {
return fmt.Errorf("instance not found")
}
switch outputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(resp, "", " ")
if err != nil {
return fmt.Errorf("marshal instance: %w", err)
}
p.Outputln(string(details))
return nil
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true), yaml.UseJSONMarshaler())
if err != nil {
return fmt.Errorf("marshal instance: %w", err)
}
p.Outputln(string(details))
return nil
default:
table := tables.NewTable()
if id := resp.Id; id != nil {
table.AddRow("ID", *id)
table.AddSeparator()
}
if name := resp.Name; name != nil {
table.AddRow("NAME", *name)
table.AddSeparator()
}
if url := resp.Url; url != nil {
table.AddRow("URL", *url)
table.AddSeparator()
}
if version := resp.Version; version != nil {
table.AddRow("VERSION", *version)
table.AddSeparator()
}
if state := resp.State; state != nil {
table.AddRow("STATE", *state)
table.AddSeparator()
}
if created := resp.Created; created != nil {
table.AddRow("CREATED", *created)
table.AddSeparator()
}
if err := table.Display(p); err != nil {
return fmt.Errorf("render table: %w", err)
}
return nil
}
}
package instance
import (
"github.com/stackitcloud/stackit-cli/internal/cmd/git/instance/create"
"github.com/stackitcloud/stackit-cli/internal/cmd/git/instance/delete"
"github.com/stackitcloud/stackit-cli/internal/cmd/git/instance/describe"
"github.com/stackitcloud/stackit-cli/internal/cmd/git/instance/list"
"github.com/stackitcloud/stackit-cli/internal/cmd/params"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/spf13/cobra"
)
func NewCmd(params *params.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: "instance",
Short: "Provides functionality for STACKIT Git instances",
Long: "Provides functionality for STACKIT Git instances.",
Args: args.NoArgs,
Run: utils.CmdHelp,
}
addSubcommands(cmd, params)
return cmd
}
func addSubcommands(cmd *cobra.Command, params *params.CmdParams) {
cmd.AddCommand(
list.NewCmd(params),
describe.NewCmd(params),
create.NewCmd(params),
delete.NewCmd(params),
)
}
package list
import (
"context"
"strconv"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/stackitcloud/stackit-cli/internal/cmd/params"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/git"
)
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &git.APIClient{}
var testProjectId = uuid.NewString()
const (
testLimit = 10
)
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
globalflags.ProjectIdFlag: testProjectId,
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
ProjectId: testProjectId,
Verbosity: globalflags.VerbosityDefault,
},
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureRequest(mods ...func(request *git.ApiListInstancesRequest)) git.ApiListInstancesRequest {
request := testClient.ListInstances(testCtx, testProjectId)
for _, mod := range mods {
mod(&request)
}
return request
}
func TestParseInput(t *testing.T) {
tests := []struct {
description string
flagValues map[string]string
isValid bool
expectedModel *inputModel
}{
{
description: "base",
flagValues: fixtureFlagValues(),
isValid: true,
expectedModel: fixtureInputModel(),
},
{
description: "no values",
flagValues: map[string]string{},
isValid: false,
},
{
description: "project id missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, globalflags.ProjectIdFlag)
}),
isValid: false,
},
{
description: "project id invalid 1",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[globalflags.ProjectIdFlag] = ""
}),
isValid: false,
},
{
description: "project id invalid 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[globalflags.ProjectIdFlag] = "invalid-uuid"
}),
isValid: false,
},
{
description: "with limit flag",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues["limit"] = strconv.Itoa(testLimit)
}),
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.Limit = utils.Ptr(int64(testLimit))
}),
},
{
description: "with limit flag == 0",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues["limit"] = strconv.Itoa(0)
}),
isValid: false,
},
{
description: "with limit flag < 0",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues["limit"] = strconv.Itoa(-1)
}),
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
p := print.NewPrinter()
cmd := NewCmd(&params.CmdParams{Printer: p})
err := globalflags.Configure(cmd.Flags())
if err != nil {
t.Fatalf("configure global flags: %v", err)
}
for flag, value := range tt.flagValues {
err := cmd.Flags().Set(flag, value)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("setting flag --%s=%s: %v", flag, value, err)
}
}
err = cmd.ValidateRequiredFlags()
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error validating flags: %v", err)
}
model, err := parseInput(p, cmd)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error parsing flags: %v", err)
}
if !tt.isValid {
t.Fatalf("did not fail on invalid input")
}
diff := cmp.Diff(model, tt.expectedModel)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest git.ApiListInstancesRequest
}{
{
description: "base",
model: fixtureInputModel(),
expectedRequest: fixtureRequest(),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
request := buildRequest(testCtx, tt.model, testClient)
diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmpopts.EquateComparable(testCtx),
)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
func TestOutputResult(t *testing.T) {
type args struct {
outputFormat string
instances []git.Instance
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "empty",
args: args{},
wantErr: false,
},
{
name: "set empty instances slice",
args: args{
instances: []git.Instance{},
},
wantErr: false,
},
{
name: "set empty instances in instances slice",
args: args{
instances: []git.Instance{{}},
},
wantErr: false,
},
}
p := print.NewPrinter()
p.Cmd = NewCmd(&params.CmdParams{Printer: p})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := outputResult(p, tt.args.outputFormat, tt.args.instances); (err != nil) != tt.wantErr {
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
package list
import (
"context"
"encoding/json"
"fmt"
"github.com/stackitcloud/stackit-cli/internal/cmd/params"
"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/flags"
"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/git/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/git"
)
type inputModel struct {
*globalflags.GlobalFlagModel
Limit *int64
}
const limitFlag = "limit"
func NewCmd(params *params.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: "Lists all instances of STACKIT Git.",
Long: "Lists all instances of STACKIT Git for the current project.",
Args: args.NoArgs,
Example: examples.Build(
examples.NewExample(
`List all STACKIT Git instances`,
"$ stackit git instance list"),
examples.NewExample(
"Lists up to 10 STACKIT Git instances",
"$ stackit git instance list --limit=10",
),
),
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := context.Background()
model, err := parseInput(params.Printer, cmd)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(params.Printer)
if err != nil {
return err
}
// Call API
req := buildRequest(ctx, model, apiClient)
resp, err := req.Execute()
if err != nil {
return fmt.Errorf("get STACKIT Git instances: %w", err)
}
instances := *resp.Instances
if len(instances) == 0 {
projectLabel, err := projectname.GetProjectName(ctx, params.Printer, params.CliVersion, cmd)
if err != nil {
params.Printer.Debug(print.ErrorLevel, "get project name: %v", err)
projectLabel = model.ProjectId
}
params.Printer.Info("No instances found for project %q\n", projectLabel)
return nil
} else if model.Limit != nil && len(instances) > int(*model.Limit) {
instances = (instances)[:*model.Limit]
}
return outputResult(params.Printer, model.OutputFormat, instances)
},
}
configureFlags(cmd)
return cmd
}
func configureFlags(cmd *cobra.Command) {
cmd.Flags().Int64(limitFlag, 0, "Limit the output to the first n elements")
}
func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)
if globalFlags.ProjectId == "" {
return nil, &errors.ProjectIdError{}
}
limit := flags.FlagToInt64Pointer(p, cmd, limitFlag)
if limit != nil && *limit < 1 {
return nil, &errors.FlagValidationError{
Flag: limitFlag,
Details: "must be greater than 0",
}
}
model := inputModel{
GlobalFlagModel: globalFlags,
Limit: limit,
}
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 *git.APIClient) git.ApiListInstancesRequest {
return apiClient.ListInstances(ctx, model.ProjectId)
}
func outputResult(p *print.Printer, outputFormat string, instances []git.Instance) error {
switch outputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(instances, "", " ")
if err != nil {
return fmt.Errorf("marshal Observability instance list: %w", err)
}
p.Outputln(string(details))
return nil
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(instances, yaml.IndentSequence(true), yaml.UseJSONMarshaler())
if err != nil {
return fmt.Errorf("marshal Observability instance list: %w", err)
}
p.Outputln(string(details))
return nil
default:
table := tables.NewTable()
table.SetHeader("ID", "NAME", "URL", "VERSION", "STATE", "CREATED")
for i := range instances {
instance := (instances)[i]
table.AddRow(
utils.PtrString(instance.Id),
utils.PtrString(instance.Name),
utils.PtrString(instance.Url),
utils.PtrString(instance.Version),
utils.PtrString(instance.State),
utils.PtrString(instance.Created),
)
}
err := table.Display(p)
if err != nil {
return fmt.Errorf("render table: %w", err)
}
return nil
}
}
package hibernate
import (
"context"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-sdk-go/services/ske"
)
type testCtxKey struct{}
const (
testRegion = "eu01"
testClusterName = "my-cluster"
)
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &ske.APIClient{}
var testProjectId = uuid.NewString()
func fixtureArgValues(mods ...func(argValues []string)) []string {
argValues := []string{
testClusterName,
}
for _, mod := range mods {
mod(argValues)
}
return argValues
}
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
globalflags.ProjectIdFlag: testProjectId,
globalflags.RegionFlag: testRegion,
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
ProjectId: testProjectId,
Region: testRegion,
Verbosity: globalflags.VerbosityDefault,
},
ClusterName: testClusterName,
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureRequest(mods ...func(request *ske.ApiTriggerHibernateRequest)) ske.ApiTriggerHibernateRequest {
request := testClient.TriggerHibernate(testCtx, testProjectId, testRegion, testClusterName)
for _, mod := range mods {
mod(&request)
}
return request
}
func TestParseInput(t *testing.T) {
tests := []struct {
description string
argValues []string
flagValues map[string]string
isValid bool
expectedModel *inputModel
}{
{
description: "base",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(),
isValid: true,
expectedModel: fixtureInputModel(),
},
{
description: "missing project id",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(fv map[string]string) {
delete(fv, globalflags.ProjectIdFlag)
}),
isValid: false,
},
{
description: "invalid project id - empty string",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(fv map[string]string) {
fv[globalflags.ProjectIdFlag] = ""
}),
isValid: false,
},
{
description: "invalid uuid format",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(fv map[string]string) {
fv[globalflags.ProjectIdFlag] = "not-a-uuid"
}),
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
p := print.NewPrinter()
cmd := &cobra.Command{}
err := globalflags.Configure(cmd.Flags())
if err != nil {
t.Fatalf("configure global flags: %v", err)
}
for flag, value := range tt.flagValues {
err := cmd.Flags().Set(flag, value)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("setting flag --%s=%s: %v", flag, value, err)
}
}
if len(tt.argValues) == 0 {
_, err := parseInput(p, cmd, tt.argValues)
if err == nil && !tt.isValid {
t.Fatalf("expected error due to missing args")
}
return
}
model, err := parseInput(p, cmd, tt.argValues)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error parsing flags: %v", err)
}
if !tt.isValid {
t.Fatalf("did not fail on invalid input")
}
diff := cmp.Diff(model, tt.expectedModel)
if diff != "" {
t.Fatalf("data does not match:\n%s", diff)
}
})
}
}
func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest ske.ApiTriggerHibernateRequest
}{
{
description: "base",
model: fixtureInputModel(),
expectedRequest: fixtureRequest(),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
request := buildRequest(testCtx, tt.model, testClient)
diff := cmp.Diff(request, tt.expectedRequest,
cmpopts.EquateComparable(testCtx),
cmp.AllowUnexported(tt.expectedRequest),
)
if diff != "" {
t.Fatalf("request mismatch:\n%s", diff)
}
})
}
}
package hibernate
import (
"context"
"fmt"
"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/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/ske/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/spinner"
"github.com/stackitcloud/stackit-sdk-go/services/ske"
"github.com/stackitcloud/stackit-sdk-go/services/ske/wait"
)
const (
clusterNameArg = "CLUSTER_NAME"
)
type inputModel struct {
*globalflags.GlobalFlagModel
ClusterName string
}
func NewCmd(params *params.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: fmt.Sprintf("hibernate %s", clusterNameArg),
Short: "Trigger hibernate for a SKE cluster",
Long: "Trigger hibernate for a STACKIT Kubernetes Engine (SKE) cluster.",
Args: args.SingleArg(clusterNameArg, nil),
Example: examples.Build(
examples.NewExample(
`Trigger hibernate for a SKE cluster with name "my-cluster"`,
"$ stackit ske cluster hibernate my-cluster"),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(params.Printer, cmd, args)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion)
if err != nil {
return err
}
projectLabel, err := projectname.GetProjectName(ctx, params.Printer, params.CliVersion, cmd)
if err != nil {
params.Printer.Debug(print.ErrorLevel, "get project name: %v", err)
projectLabel = model.ProjectId
}
if !model.AssumeYes {
prompt := fmt.Sprintf("Are you sure you want to trigger hibernate for %q in project %q?", model.ClusterName, projectLabel)
err = params.Printer.PromptForConfirmation(prompt)
if err != nil {
return err
}
}
// Call API
req := buildRequest(ctx, model, apiClient)
_, err = req.Execute()
if err != nil {
return fmt.Errorf("hibernate SKE cluster: %w", err)
}
// Wait for async operation, if async mode not enabled
if !model.Async {
s := spinner.New(params.Printer)
s.Start("Hibernating cluster")
_, err = wait.TriggerClusterHibernationWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ClusterName).WaitWithContext(ctx)
if err != nil {
return fmt.Errorf("wait for SKE cluster hibernation: %w", err)
}
s.Stop()
}
operationState := "Hibernated"
if model.Async {
operationState = "Triggered hibernation of"
}
params.Printer.Outputf("%s cluster %q\n", operationState, model.ClusterName)
return nil
},
}
return cmd
}
func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) {
clusterName := inputArgs[0]
globalFlags := globalflags.Parse(p, cmd)
if globalFlags.ProjectId == "" {
return nil, &errors.ProjectIdError{}
}
model := inputModel{
GlobalFlagModel: globalFlags,
ClusterName: clusterName,
}
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 *ske.APIClient) ske.ApiTriggerHibernateRequest {
req := apiClient.TriggerHibernate(ctx, model.ProjectId, model.Region, model.ClusterName)
return req
}
package maintenance
import (
"context"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-sdk-go/services/ske"
)
type testCtxKey struct{}
const (
testRegion = "eu01"
testClusterName = "my-cluster"
)
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &ske.APIClient{}
var testProjectId = uuid.NewString()
func fixtureArgValues(mods ...func([]string)) []string {
argValues := []string{
testClusterName,
}
for _, m := range mods {
m(argValues)
}
return argValues
}
func fixtureFlagValues(mods ...func(map[string]string)) map[string]string {
flagValues := map[string]string{
globalflags.ProjectIdFlag: testProjectId,
globalflags.RegionFlag: testRegion,
}
for _, m := range mods {
m(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(*inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
ProjectId: testProjectId,
Region: testRegion,
Verbosity: globalflags.VerbosityDefault,
},
ClusterName: testClusterName,
}
for _, m := range mods {
m(model)
}
return model
}
func fixtureRequest(mods ...func(*ske.ApiTriggerMaintenanceRequest)) ske.ApiTriggerMaintenanceRequest {
request := testClient.TriggerMaintenance(testCtx, testProjectId, testRegion, testClusterName)
for _, m := range mods {
m(&request)
}
return request
}
func TestParseInput(t *testing.T) {
tests := []struct {
description string
argValues []string
flagValues map[string]string
isValid bool
expectedModel *inputModel
}{
{
description: "base",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(),
isValid: true,
expectedModel: fixtureInputModel(),
},
{
description: "missing project id",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(fv map[string]string) {
delete(fv, globalflags.ProjectIdFlag)
}),
isValid: false,
},
{
description: "invalid project id - empty string",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(fv map[string]string) {
fv[globalflags.ProjectIdFlag] = ""
}),
isValid: false,
},
{
description: "invalid uuid format",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(fv map[string]string) {
fv[globalflags.ProjectIdFlag] = "not-a-uuid"
}),
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
p := print.NewPrinter()
cmd := &cobra.Command{}
err := globalflags.Configure(cmd.Flags())
if err != nil {
t.Fatalf("configure global flags: %v", err)
}
for flag, value := range tt.flagValues {
err := cmd.Flags().Set(flag, value)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("setting flag --%s=%s: %v", flag, value, err)
}
}
if len(tt.argValues) == 0 {
_, err := parseInput(p, cmd, tt.argValues)
if err == nil && !tt.isValid {
t.Fatalf("expected error due to missing args")
}
return
}
model, err := parseInput(p, cmd, tt.argValues)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error parsing input: %v", err)
}
if !tt.isValid {
t.Fatalf("did not fail on invalid input")
}
diff := cmp.Diff(model, tt.expectedModel)
if diff != "" {
t.Fatalf("input model mismatch:\n%s", diff)
}
})
}
}
func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest ske.ApiTriggerMaintenanceRequest
}{
{
description: "base",
model: fixtureInputModel(),
expectedRequest: fixtureRequest(),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
got := buildRequest(testCtx, tt.model, testClient)
want := tt.expectedRequest
diff := cmp.Diff(got, want,
cmpopts.EquateComparable(testCtx),
cmp.AllowUnexported(want),
)
if diff != "" {
t.Fatalf("request mismatch:\n%s", diff)
}
})
}
}
package maintenance
import (
"context"
"fmt"
"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/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/ske/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/spinner"
"github.com/stackitcloud/stackit-sdk-go/services/ske"
"github.com/stackitcloud/stackit-sdk-go/services/ske/wait"
)
const (
clusterNameArg = "CLUSTER_NAME"
)
type inputModel struct {
*globalflags.GlobalFlagModel
ClusterName string
}
func NewCmd(params *params.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: fmt.Sprintf("maintenance %s", clusterNameArg),
Short: "Trigger maintenance for a SKE cluster",
Long: "Trigger maintenance for a STACKIT Kubernetes Engine (SKE) cluster.",
Args: args.SingleArg(clusterNameArg, nil),
Example: examples.Build(
examples.NewExample(
`Trigger maintenance for a SKE cluster with name "my-cluster"`,
"$ stackit ske cluster maintenance my-cluster"),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(params.Printer, cmd, args)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion)
if err != nil {
return err
}
projectLabel, err := projectname.GetProjectName(ctx, params.Printer, params.CliVersion, cmd)
if err != nil {
params.Printer.Debug(print.ErrorLevel, "get project name: %v", err)
projectLabel = model.ProjectId
}
if !model.AssumeYes {
prompt := fmt.Sprintf("Are you sure you want to trigger maintenance for %q in project %q?", model.ClusterName, projectLabel)
err = params.Printer.PromptForConfirmation(prompt)
if err != nil {
return err
}
}
// Call API
req := buildRequest(ctx, model, apiClient)
_, err = req.Execute()
if err != nil {
return fmt.Errorf("trigger maintenance SKE cluster: %w", err)
}
// Wait for async operation, if async mode not enabled
if !model.Async {
s := spinner.New(params.Printer)
s.Start("Performing cluster maintenance")
_, err = wait.TriggerClusterMaintenanceWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ClusterName).WaitWithContext(ctx)
if err != nil {
return fmt.Errorf("wait for SKE cluster maintenance to complete: %w", err)
}
s.Stop()
}
operationState := "Performed maintenance for"
if model.Async {
operationState = "Triggered maintenance for"
}
params.Printer.Outputf("%s cluster %q\n", operationState, model.ClusterName)
return nil
},
}
return cmd
}
func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) {
clusterName := inputArgs[0]
globalFlags := globalflags.Parse(p, cmd)
if globalFlags.ProjectId == "" {
return nil, &errors.ProjectIdError{}
}
model := inputModel{
GlobalFlagModel: globalFlags,
ClusterName: clusterName,
}
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 *ske.APIClient) ske.ApiTriggerMaintenanceRequest {
req := apiClient.TriggerMaintenance(ctx, model.ProjectId, model.Region, model.ClusterName)
return req
}
package reconcile
import (
"context"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-sdk-go/services/ske"
)
type testCtxKey struct{}
const (
testRegion = "eu01"
testClusterName = "my-cluster"
)
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &ske.APIClient{}
var testProjectId = uuid.NewString()
func fixtureArgValues(mods ...func([]string)) []string {
argValues := []string{
testClusterName,
}
for _, m := range mods {
m(argValues)
}
return argValues
}
func fixtureFlagValues(mods ...func(map[string]string)) map[string]string {
flagValues := map[string]string{
globalflags.ProjectIdFlag: testProjectId,
globalflags.RegionFlag: testRegion,
}
for _, m := range mods {
m(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(*inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
ProjectId: testProjectId,
Region: testRegion,
Verbosity: globalflags.VerbosityDefault,
},
ClusterName: testClusterName,
}
for _, m := range mods {
m(model)
}
return model
}
func fixtureRequest(mods ...func(request *ske.ApiTriggerReconcileRequest)) ske.ApiTriggerHibernateRequest {
request := testClient.TriggerReconcile(testCtx, testProjectId, testRegion, testClusterName)
for _, m := range mods {
m(&request)
}
return request
}
func TestParseInput(t *testing.T) {
tests := []struct {
description string
argValues []string
flagValues map[string]string
isValid bool
expectedModel *inputModel
}{
{
description: "base",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(),
isValid: true,
expectedModel: fixtureInputModel(),
},
{
description: "missing project id",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(fv map[string]string) {
delete(fv, globalflags.ProjectIdFlag)
}),
isValid: false,
},
{
description: "invalid project id - empty string",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(fv map[string]string) {
fv[globalflags.ProjectIdFlag] = ""
}),
isValid: false,
},
{
description: "invalid uuid format",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(fv map[string]string) {
fv[globalflags.ProjectIdFlag] = "not-a-uuid"
}),
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
p := print.NewPrinter()
cmd := &cobra.Command{}
err := globalflags.Configure(cmd.Flags())
if err != nil {
t.Fatalf("configure global flags: %v", err)
}
for flag, value := range tt.flagValues {
err := cmd.Flags().Set(flag, value)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("setting flag --%s=%s: %v", flag, value, err)
}
}
if len(tt.argValues) == 0 {
_, err := parseInput(p, cmd, tt.argValues)
if err == nil && !tt.isValid {
t.Fatalf("expected error due to missing args")
}
return
}
model, err := parseInput(p, cmd, tt.argValues)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error parsing input: %v", err)
}
if !tt.isValid {
t.Fatalf("did not fail on invalid input")
}
diff := cmp.Diff(model, tt.expectedModel)
if diff != "" {
t.Fatalf("input model mismatch:\n%s", diff)
}
})
}
}
func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest ske.ApiTriggerHibernateRequest
}{
{
description: "base",
model: fixtureInputModel(),
expectedRequest: fixtureRequest(),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
got := buildRequest(testCtx, tt.model, testClient)
want := tt.expectedRequest
diff := cmp.Diff(got, want,
cmpopts.EquateComparable(testCtx),
cmp.AllowUnexported(want),
)
if diff != "" {
t.Fatalf("request mismatch:\n%s", diff)
}
})
}
}
package reconcile
import (
"context"
"fmt"
"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/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/spinner"
"github.com/stackitcloud/stackit-sdk-go/services/ske"
"github.com/stackitcloud/stackit-sdk-go/services/ske/wait"
)
const (
clusterNameArg = "CLUSTER_NAME"
)
type inputModel struct {
*globalflags.GlobalFlagModel
ClusterName string
}
func NewCmd(params *params.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: fmt.Sprintf("reconcile %s", clusterNameArg),
Short: "Trigger reconcile for a SKE cluster",
Long: "Trigger reconcile for a STACKIT Kubernetes Engine (SKE) cluster.",
Args: args.SingleArg(clusterNameArg, nil),
Example: examples.Build(
examples.NewExample(
`Trigger reconcile for a SKE cluster with name "my-cluster"`,
"$ stackit ske cluster reconcile my-cluster"),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(params.Printer, cmd, args)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion)
if err != nil {
return err
}
// Call API
req := buildRequest(ctx, model, apiClient)
_, err = req.Execute()
if err != nil {
return fmt.Errorf("reconcile SKE cluster: %w", err)
}
// Wait for async operation, if async mode not enabled
if !model.Async {
s := spinner.New(params.Printer)
s.Start("Performing cluster reconciliation")
_, err = wait.TriggerClusterReconciliationWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ClusterName).WaitWithContext(ctx)
if err != nil {
return fmt.Errorf("wait for SKE cluster reconciliation: %w", err)
}
s.Stop()
}
operationState := "Performed reconciliation for"
if model.Async {
operationState = "Triggered reconcile for"
}
params.Printer.Outputf("%s cluster %q\n", operationState, model.ClusterName)
return nil
},
}
return cmd
}
func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) {
clusterName := inputArgs[0]
globalFlags := globalflags.Parse(p, cmd)
if globalFlags.ProjectId == "" {
return nil, &errors.ProjectIdError{}
}
model := inputModel{
GlobalFlagModel: globalFlags,
ClusterName: clusterName,
}
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 *ske.APIClient) ske.ApiTriggerReconcileRequest {
req := apiClient.TriggerReconcile(ctx, model.ProjectId, model.Region, model.ClusterName)
return req
}
package wakeup
import (
"context"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-sdk-go/services/ske"
)
type testCtxKey struct{}
const (
testRegion = "eu01"
testClusterName = "my-cluster"
)
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &ske.APIClient{}
var testProjectId = uuid.NewString()
func fixtureArgValues(mods ...func([]string)) []string {
argValues := []string{testClusterName}
for _, m := range mods {
m(argValues)
}
return argValues
}
func fixtureFlagValues(mods ...func(map[string]string)) map[string]string {
flags := map[string]string{
globalflags.ProjectIdFlag: testProjectId,
globalflags.RegionFlag: testRegion,
}
for _, m := range mods {
m(flags)
}
return flags
}
func fixtureInputModel(mods ...func(*inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
ProjectId: testProjectId,
Region: testRegion,
Verbosity: globalflags.VerbosityDefault,
},
ClusterName: testClusterName,
}
for _, m := range mods {
m(model)
}
return model
}
func fixtureRequest(mods ...func(*ske.ApiTriggerWakeupRequest)) ske.ApiTriggerWakeupRequest {
req := testClient.TriggerWakeup(testCtx, testProjectId, testRegion, testClusterName)
for _, m := range mods {
m(&req)
}
return req
}
func TestParseInput(t *testing.T) {
tests := []struct {
description string
argValues []string
flagValues map[string]string
isValid bool
expectedModel *inputModel
}{
{
description: "base",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(),
isValid: true,
expectedModel: fixtureInputModel(),
},
{
description: "missing project id",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(fv map[string]string) {
delete(fv, globalflags.ProjectIdFlag)
}),
isValid: false,
},
{
description: "invalid project id - empty string",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(fv map[string]string) {
fv[globalflags.ProjectIdFlag] = ""
}),
isValid: false,
},
{
description: "invalid uuid format",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(fv map[string]string) {
fv[globalflags.ProjectIdFlag] = "not-a-uuid"
}),
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
p := print.NewPrinter()
cmd := &cobra.Command{}
err := globalflags.Configure(cmd.Flags())
if err != nil {
t.Fatalf("configure global flags: %v", err)
}
for flag, value := range tt.flagValues {
err := cmd.Flags().Set(flag, value)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("setting flag --%s=%s: %v", flag, value, err)
}
}
if len(tt.argValues) == 0 {
_, err := parseInput(p, cmd, tt.argValues)
if err == nil && !tt.isValid {
t.Fatalf("expected failure due to missing args")
}
return
}
model, err := parseInput(p, cmd, tt.argValues)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("unexpected error: %v", err)
}
if !tt.isValid {
t.Fatalf("did not fail on invalid input")
}
diff := cmp.Diff(model, tt.expectedModel)
if diff != "" {
t.Fatalf("input model mismatch:\n%s", diff)
}
})
}
}
func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest ske.ApiTriggerHibernateRequest
}{
{
description: "base",
model: fixtureInputModel(),
expectedRequest: fixtureRequest(),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
got := buildRequest(testCtx, tt.model, testClient)
want := tt.expectedRequest
diff := cmp.Diff(got, want,
cmpopts.EquateComparable(testCtx),
cmp.AllowUnexported(want),
)
if diff != "" {
t.Fatalf("request mismatch:\n%s", diff)
}
})
}
}
package wakeup
import (
"context"
"fmt"
"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/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/spinner"
"github.com/stackitcloud/stackit-sdk-go/services/ske"
"github.com/stackitcloud/stackit-sdk-go/services/ske/wait"
)
const (
clusterNameArg = "CLUSTER_NAME"
)
type inputModel struct {
*globalflags.GlobalFlagModel
ClusterName string
}
func NewCmd(params *params.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: fmt.Sprintf("wakeup %s", clusterNameArg),
Short: "Trigger wakeup from hibernation for a SKE cluster",
Long: "Trigger wakeup from hibernation for a STACKIT Kubernetes Engine (SKE) cluster.",
Args: args.SingleArg(clusterNameArg, nil),
Example: examples.Build(
examples.NewExample(
`Trigger wakeup from hibernation for a SKE cluster with name "my-cluster"`,
"$ stackit ske cluster wakeup my-cluster"),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(params.Printer, cmd, args)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion)
if err != nil {
return err
}
// Call API
req := buildRequest(ctx, model, apiClient)
_, err = req.Execute()
if err != nil {
return fmt.Errorf("wakeup SKE cluster: %w", err)
}
// Wait for async operation, if async mode not enabled
if !model.Async {
s := spinner.New(params.Printer)
s.Start("Performing cluster wakeup")
_, err = wait.TriggerClusterWakeupWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ClusterName).WaitWithContext(ctx)
if err != nil {
return fmt.Errorf("wait for SKE cluster wakeup: %w", err)
}
s.Stop()
}
operationState := "Performed wakeup of"
if model.Async {
operationState = "Triggered wakeup of"
}
params.Printer.Outputf("%s cluster %q\n", operationState, model.ClusterName)
return nil
},
}
return cmd
}
func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) {
clusterName := inputArgs[0]
globalFlags := globalflags.Parse(p, cmd)
if globalFlags.ProjectId == "" {
return nil, &errors.ProjectIdError{}
}
model := inputModel{
GlobalFlagModel: globalFlags,
ClusterName: clusterName,
}
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 *ske.APIClient) ske.ApiTriggerWakeupRequest {
req := apiClient.TriggerWakeup(ctx, model.ProjectId, model.Region, model.ClusterName)
return req
}
#!/bin/bash
# This script is used to publish new packages to the CLI RPM repository
# Usage: ./publish-rpm-packages.sh
set -eo pipefail
ROOT_DIR=$(git rev-parse --show-toplevel)
PACKAGES_BUCKET_URL="https://packages.stackit.cloud"
RPM_REPO_PATH="rpm/cli"
RPM_BUCKET_NAME="distribution"
CUSTOM_KEYRING_FILE="rpm-keyring.gpg"
GORELEASER_PACKAGES_FOLDER="dist/"
TEMP_DIR=$(mktemp -d)
# We need to disable the key database daemon (keyboxd)
# This can be done by removing "use-keyboxd" from ~/.gnupg/common.conf (see https://github.com/gpg/gnupg/blob/master/README)
echo -n >~/.gnupg/common.conf
# Create a local mirror of the current state of the remote RPM repository
printf ">>> Creating mirror \n"
curl ${PACKAGES_BUCKET_URL}/${RPM_REPO_PATH}/repodata/repomd.xml >${TEMP_DIR}/repomd.xml || echo "No existing repository found, creating new one"
# Create RPM repository structure
mkdir -p ${TEMP_DIR}/rpm-repo/RPMS
# Copy existing RPMs from remote repository (if any)
printf "\n>>> Downloading existing RPMs \n"
aws s3 sync s3://${RPM_BUCKET_NAME}/${RPM_REPO_PATH}/RPMS/ ${TEMP_DIR}/rpm-repo/RPMS/ --endpoint-url https://object.storage.eu01.onstackit.cloud || echo "No existing RPMs found"
# Copy new generated .rpm packages to the local repo
# Note: GoReleaser already signs these RPM packages with embedded signatures
printf "\n>>> Adding new packages to local repo \n"
cp ${GORELEASER_PACKAGES_FOLDER}/*.rpm ${TEMP_DIR}/rpm-repo/RPMS/
# Create RPM repository metadata using createrepo_c
printf "\n>>> Creating RPM repository metadata \n"
docker run --rm \
-v "${TEMP_DIR}/rpm-repo:/repo" \
fedora:latest \
bash -c "
# Install createrepo_c
dnf install -y createrepo_c
# Create repository metadata
createrepo_c /repo
"
# Sign the repository metadata using the same GPG key as APT
if [ -n "$GPG_PRIVATE_KEY_FINGERPRINT" ] && [ -n "$GPG_PASSPHRASE" ]; then
printf "\n>>> Signing repository metadata \n"
gpg --batch --yes --pinentry-mode loopback --local-user="${GPG_PRIVATE_KEY_FINGERPRINT}" --passphrase="${GPG_PASSPHRASE}" --detach-sign --armor ${TEMP_DIR}/rpm-repo/repodata/repomd.xml
else
echo ">>> Skipping repository metadata signing (GPG environment variables not set)"
fi
# Upload to S3
printf "\n>>> Uploading to S3 \n"
aws s3 sync ${TEMP_DIR}/rpm-repo/ s3://${RPM_BUCKET_NAME}/${RPM_REPO_PATH}/ --endpoint-url https://object.storage.eu01.onstackit.cloud
# Clean up
rm -rf ${TEMP_DIR}
printf "\n>>> RPM repository published successfully to ${PACKAGES_BUCKET_URL}/${RPM_REPO_PATH} \n"
+4
-2

@@ -67,5 +67,7 @@ name: CI

steps:
- uses: fgrosse/go-coverage-report@v1.2.0
- name: Check new code coverage
uses: fgrosse/go-coverage-report@v1.2.0
continue-on-error: true # Add this line to prevent pipeline failures in forks
with:
coverage-artifact-name: ${{ env.CODE_COVERAGE_ARTIFACT_NAME }}
coverage-file-name: ${{ env.CODE_COVERAGE_FILE_NAME }}
coverage-file-name: ${{ env.CODE_COVERAGE_FILE_NAME }}

@@ -78,2 +78,4 @@ # STACKIT CLI release workflow.

GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}
- name: Install createrepo_c
run: brew install createrepo_c
- name: Publish packages to APT repo

@@ -85,1 +87,7 @@ if: contains(github.ref_name, '-') == false

run: ./scripts/publish-apt-packages.sh
- name: Publish packages to RPM repo
if: contains(github.ref_name, '-') == false
env:
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
GPG_PRIVATE_KEY_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}
run: ./scripts/publish-rpm-packages.sh

@@ -16,5 +16,5 @@ name: Renovate

- name: Self-hosted Renovate
uses: renovatebot/github-action@v43.0.2
uses: renovatebot/github-action@v43.0.5
with:
configurationFile: .github/renovate.json
token: ${{ secrets.RENOVATE_TOKEN }}

@@ -168,4 +168,3 @@ version: 2

# if the tag has a prerelease indicator (e.g. v0.0.1-alpha1)
# Temporarily not skipping prereleases to test integration with Winget
# skip_upload: auto
skip_upload: auto
repository:

@@ -172,0 +171,0 @@ owner: stackitcloud

@@ -33,6 +33,4 @@ ## stackit git

* [stackit](./stackit.md) - Manage STACKIT resources using the command line
* [stackit git create](./stackit_git_create.md) - Creates STACKIT Git instance
* [stackit git delete](./stackit_git_delete.md) - Deletes STACKIT Git instance
* [stackit git describe](./stackit_git_describe.md) - Describes STACKIT Git instance
* [stackit git list](./stackit_git_list.md) - Lists all instances of STACKIT Git.
* [stackit git flavor](./stackit_git_flavor.md) - Provides functionality for STACKIT Git flavors
* [stackit git instance](./stackit_git_instance.md) - Provides functionality for STACKIT Git instances

@@ -32,3 +32,3 @@ ## stackit mongodbflex user create

--instance-id string ID of the instance
--role strings Roles of the user, possible values are ["read" "readWrite"] (default [read])
--role strings Roles of the user, possible values are ["read" "readWrite" "readWriteAnyDatabase"] (default [read])
--username string Username of the user. If not specified, a random username will be assigned

@@ -35,0 +35,0 @@ ```

@@ -26,3 +26,3 @@ ## stackit mongodbflex user update

--instance-id string ID of the instance
--role strings Roles of the user, possible values are ["read" "readWrite"] (default [])
--role strings Roles of the user, possible values are ["read" "readWrite" "readWriteAnyDatabase"] (default [])
```

@@ -29,0 +29,0 @@

## stackit ske cluster create
Creates an SKE cluster
Creates a SKE cluster

@@ -18,9 +18,9 @@ ### Synopsis

```
Create an SKE cluster using default configuration
Create a SKE cluster using default configuration
$ stackit ske cluster create my-cluster
Create an SKE cluster using an API payload sourced from the file "./payload.json"
Create a SKE cluster using an API payload sourced from the file "./payload.json"
$ stackit ske cluster create my-cluster --payload @./payload.json
Create an SKE cluster using an API payload provided as a JSON string
Create a SKE cluster using an API payload provided as a JSON string
$ stackit ske cluster create my-cluster --payload "{...}"

@@ -27,0 +27,0 @@

@@ -16,3 +16,3 @@ ## stackit ske cluster delete

```
Delete an SKE cluster with name "my-cluster"
Delete a SKE cluster with name "my-cluster"
$ stackit ske cluster delete my-cluster

@@ -19,0 +19,0 @@ ```

## stackit ske cluster describe
Shows details of a SKE cluster
Shows details of a SKE cluster
### Synopsis
Shows details of a STACKIT Kubernetes Engine (SKE) cluster.
Shows details of a STACKIT Kubernetes Engine (SKE) cluster.

@@ -16,6 +16,6 @@ ```

```
Get details of an SKE cluster with name "my-cluster"
Get details of a SKE cluster with name "my-cluster"
$ stackit ske cluster describe my-cluster
Get details of an SKE cluster with name "my-cluster" in JSON format
Get details of a SKE cluster with name "my-cluster" in JSON format
$ stackit ske cluster describe my-cluster --output-format json

@@ -22,0 +22,0 @@ ```

## stackit ske cluster update
Updates an SKE cluster
Updates a SKE cluster

@@ -18,6 +18,6 @@ ### Synopsis

```
Update an SKE cluster using an API payload sourced from the file "./payload.json"
Update a SKE cluster using an API payload sourced from the file "./payload.json"
$ stackit ske cluster update my-cluster --payload @./payload.json
Update an SKE cluster using an API payload provided as a JSON string
Update a SKE cluster using an API payload provided as a JSON string
$ stackit ske cluster update my-cluster --payload "{...}"

@@ -24,0 +24,0 @@

@@ -33,8 +33,12 @@ ## stackit ske cluster

* [stackit ske](./stackit_ske.md) - Provides functionality for SKE
* [stackit ske cluster create](./stackit_ske_cluster_create.md) - Creates an SKE cluster
* [stackit ske cluster create](./stackit_ske_cluster_create.md) - Creates a SKE cluster
* [stackit ske cluster delete](./stackit_ske_cluster_delete.md) - Deletes a SKE cluster
* [stackit ske cluster describe](./stackit_ske_cluster_describe.md) - Shows details of a SKE cluster
* [stackit ske cluster describe](./stackit_ske_cluster_describe.md) - Shows details of a SKE cluster
* [stackit ske cluster generate-payload](./stackit_ske_cluster_generate-payload.md) - Generates a payload to create/update SKE clusters
* [stackit ske cluster hibernate](./stackit_ske_cluster_hibernate.md) - Trigger hibernate for a SKE cluster
* [stackit ske cluster list](./stackit_ske_cluster_list.md) - Lists all SKE clusters
* [stackit ske cluster update](./stackit_ske_cluster_update.md) - Updates an SKE cluster
* [stackit ske cluster maintenance](./stackit_ske_cluster_maintenance.md) - Trigger maintenance for a SKE cluster
* [stackit ske cluster reconcile](./stackit_ske_cluster_reconcile.md) - Trigger reconcile for a SKE cluster
* [stackit ske cluster update](./stackit_ske_cluster_update.md) - Updates a SKE cluster
* [stackit ske cluster wakeup](./stackit_ske_cluster_wakeup.md) - Trigger wakeup from hibernation for a SKE cluster
## stackit ske kubeconfig create
Creates or update a kubeconfig for an SKE cluster
Creates or update a kubeconfig for a SKE cluster
### Synopsis
Creates a kubeconfig for a STACKIT Kubernetes Engine (SKE) cluster, if the config exits in the kubeconfig file the information will be updated.
Creates a kubeconfig for a STACKIT Kubernetes Engine (SKE) cluster, if the config exists in the kubeconfig file the information will be updated.

@@ -9,0 +9,0 @@ By default, the kubeconfig information of the SKE cluster is merged into the default kubeconfig file of the current user. If the kubeconfig file doesn't exist, a new one will be created.

@@ -33,4 +33,4 @@ ## stackit ske kubeconfig

* [stackit ske](./stackit_ske.md) - Provides functionality for SKE
* [stackit ske kubeconfig create](./stackit_ske_kubeconfig_create.md) - Creates or update a kubeconfig for an SKE cluster
* [stackit ske kubeconfig create](./stackit_ske_kubeconfig_create.md) - Creates or update a kubeconfig for a SKE cluster
* [stackit ske kubeconfig login](./stackit_ske_kubeconfig_login.md) - Login plugin for kubernetes clients
+37
-35

@@ -8,35 +8,35 @@ module github.com/stackitcloud/stackit-cli

github.com/goccy/go-yaml v1.18.0
github.com/golang-jwt/jwt/v5 v5.2.2
github.com/golang-jwt/jwt/v5 v5.2.3
github.com/google/go-cmp v0.7.0
github.com/google/uuid v1.6.0
github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf
github.com/jedib0t/go-pretty/v6 v6.6.7
github.com/jedib0t/go-pretty/v6 v6.6.8
github.com/lmittmann/tint v1.1.2
github.com/mattn/go-colorable v0.1.14
github.com/spf13/cobra v1.9.1
github.com/spf13/pflag v1.0.6
github.com/spf13/pflag v1.0.7
github.com/spf13/viper v1.20.1
github.com/stackitcloud/stackit-sdk-go/core v0.17.2
github.com/stackitcloud/stackit-sdk-go/services/alb v0.5.0
github.com/stackitcloud/stackit-sdk-go/services/authorization v0.8.0
github.com/stackitcloud/stackit-sdk-go/services/dns v0.17.0
github.com/stackitcloud/stackit-sdk-go/services/git v0.7.0
github.com/stackitcloud/stackit-sdk-go/services/iaas v0.26.0
github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.3.0
github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.24.0
github.com/stackitcloud/stackit-sdk-go/services/postgresflex v1.2.0
github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.17.0
github.com/stackitcloud/stackit-sdk-go/services/runcommand v1.3.0
github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.13.0
github.com/stackitcloud/stackit-sdk-go/services/serverbackup v1.3.1
github.com/stackitcloud/stackit-sdk-go/services/serverupdate v1.2.0
github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.9.0
github.com/stackitcloud/stackit-sdk-go/services/serviceenablement v1.2.1
github.com/stackitcloud/stackit-sdk-go/services/ske v1.0.0
github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.3.0
github.com/stackitcloud/stackit-sdk-go/core v0.17.3
github.com/stackitcloud/stackit-sdk-go/services/alb v0.6.1
github.com/stackitcloud/stackit-sdk-go/services/authorization v0.8.1
github.com/stackitcloud/stackit-sdk-go/services/dns v0.17.1
github.com/stackitcloud/stackit-sdk-go/services/git v0.7.1
github.com/stackitcloud/stackit-sdk-go/services/iaas v0.27.1
github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.5.1
github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.24.1
github.com/stackitcloud/stackit-sdk-go/services/postgresflex v1.2.1
github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.17.1
github.com/stackitcloud/stackit-sdk-go/services/runcommand v1.3.1
github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.13.1
github.com/stackitcloud/stackit-sdk-go/services/serverbackup v1.3.2
github.com/stackitcloud/stackit-sdk-go/services/serverupdate v1.2.1
github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.9.1
github.com/stackitcloud/stackit-sdk-go/services/serviceenablement v1.2.2
github.com/stackitcloud/stackit-sdk-go/services/ske v1.3.0
github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.3.1
github.com/zalando/go-keyring v0.2.6
golang.org/x/mod v0.25.0
golang.org/x/mod v0.26.0
golang.org/x/oauth2 v0.30.0
golang.org/x/term v0.32.0
golang.org/x/text v0.26.0
golang.org/x/term v0.33.0
golang.org/x/text v0.27.0
k8s.io/apimachinery v0.32.3

@@ -47,3 +47,3 @@ k8s.io/client-go v0.32.3

require (
golang.org/x/net v0.41.0 // indirect
golang.org/x/net v0.42.0 // indirect
golang.org/x/time v0.11.0 // indirect

@@ -211,4 +211,6 @@ gopkg.in/inf.v0 v0.9.1 // indirect

golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/tools v0.34.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/tools v0.35.0 // indirect
golang.org/x/tools/go/expect v0.1.1-deprecated // indirect
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect
google.golang.org/protobuf v1.36.6 // indirect

@@ -242,12 +244,12 @@ gopkg.in/yaml.v2 v2.4.0 // indirect

github.com/spf13/cast v1.7.1 // indirect
github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.4.0
github.com/stackitcloud/stackit-sdk-go/services/logme v0.25.0
github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.25.0
github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.3.0
github.com/stackitcloud/stackit-sdk-go/services/observability v0.8.0
github.com/stackitcloud/stackit-sdk-go/services/rabbitmq v0.25.0
github.com/stackitcloud/stackit-sdk-go/services/redis v0.25.0
github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.5.1
github.com/stackitcloud/stackit-sdk-go/services/logme v0.25.1
github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.25.1
github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.3.1
github.com/stackitcloud/stackit-sdk-go/services/observability v0.9.1
github.com/stackitcloud/stackit-sdk-go/services/rabbitmq v0.25.1
github.com/stackitcloud/stackit-sdk-go/services/redis v0.25.1
github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/sys v0.34.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

@@ -254,0 +256,0 @@ k8s.io/api v0.32.3 // indirect

@@ -20,5 +20,45 @@ # Installation

```shell
brew install stackit
brew install --cask stackit
```
#### Formula deprecated
The homebrew formula is deprecated, will no longer be updated and will be removed after 2026-01-22.
You need to install the STACKIT CLI as cask.
Therefor you need to uninstall the formula and reinstall it as cask.
Your profiles should normally remain. To ensure that nothing will be gone, you should backup them.
1. Export your existing profiles. This will create a json file in your current directory.
```shell
stackit config profile export default
```
2. If you have multiple profiles, then execute the export command for each of them. You can find your profiles via:
```shell
stackit config profile list
stackit config profile export <profile-name>
```
3. Uninstall the formula.
```shell
brew uninstall stackit
```
4. Install the STACKIT CLI as cask.
```shell
brew install --cask stackit
```
5. Check if your configs are still stored.
```shell
stackit config profile list
```
6. In case the profiles are gone, import your profiles via:
```shell
$ stackit config profile import -c @default.json --name myProfile
```
### Linux

@@ -25,0 +65,0 @@

package getaccesstoken
import (
"encoding/json"
"fmt"
"github.com/goccy/go-yaml"
"github.com/spf13/cobra"

@@ -10,4 +14,10 @@ "github.com/stackitcloud/stackit-cli/internal/cmd/params"

"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
)
type inputModel struct {
*globalflags.GlobalFlagModel
}
func NewCmd(params *params.CmdParams) *cobra.Command {

@@ -24,3 +34,8 @@ cmd := &cobra.Command{

),
RunE: func(_ *cobra.Command, _ []string) error {
RunE: func(cmd *cobra.Command, _ []string) error {
model, err := parseInput(params.Printer, cmd)
if err != nil {
return err
}
userSessionExpired, err := auth.UserSessionExpired()

@@ -34,3 +49,4 @@ if err != nil {

accessToken, err := auth.GetAccessToken()
// Try to get a valid access token, refreshing if necessary
accessToken, err := auth.RefreshAccessToken(params.Printer)
if err != nil {

@@ -40,12 +56,28 @@ return err

accessTokenExpired, err := auth.TokenExpired(accessToken)
if err != nil {
return err
switch model.OutputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(map[string]string{
"access_token": accessToken,
}, "", " ")
if err != nil {
return fmt.Errorf("marshal image list: %w", err)
}
params.Printer.Outputln(string(details))
return nil
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(map[string]string{
"access_token": accessToken,
}, yaml.IndentSequence(true), yaml.UseJSONMarshaler())
if err != nil {
return fmt.Errorf("marshal image list: %w", err)
}
params.Printer.Outputln(string(details))
return nil
default:
params.Printer.Outputln(accessToken)
return nil
}
if accessTokenExpired {
return &cliErr.AccessTokenExpiredError{}
}
params.Printer.Outputf("%s\n", accessToken)
return nil
},

@@ -55,1 +87,20 @@ }

}
func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)
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
}
package git
import (
"github.com/stackitcloud/stackit-cli/internal/cmd/git/create"
"github.com/stackitcloud/stackit-cli/internal/cmd/git/delete"
"github.com/stackitcloud/stackit-cli/internal/cmd/git/describe"
"github.com/stackitcloud/stackit-cli/internal/cmd/git/list"
"github.com/stackitcloud/stackit-cli/internal/cmd/git/flavor"
"github.com/stackitcloud/stackit-cli/internal/cmd/git/instance"
"github.com/stackitcloud/stackit-cli/internal/cmd/params"

@@ -29,7 +27,5 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/args"

cmd.AddCommand(
list.NewCmd(params),
describe.NewCmd(params),
create.NewCmd(params),
delete.NewCmd(params),
instance.NewCmd(params),
flavor.NewCmd(params),
)
}

@@ -17,3 +17,6 @@ package describe

var projectIdFlag = globalflags.ProjectIdFlag
const (
testRegion = "eu02"
testBackupId = "backupID"
)

@@ -26,3 +29,2 @@ type testCtxKey struct{}

var testInstanceId = uuid.NewString()
var testBackupId = "backupID"

@@ -41,4 +43,5 @@ func fixtureArgValues(mods ...func(argValues []string)) []string {

flagValues := map[string]string{
projectIdFlag: testProjectId,
instanceIdFlag: testInstanceId,
globalflags.ProjectIdFlag: testProjectId,
globalflags.RegionFlag: testRegion,
instanceIdFlag: testInstanceId,
}

@@ -56,2 +59,3 @@ for _, mod := range mods {

Verbosity: globalflags.VerbosityDefault,
Region: testRegion,
},

@@ -68,3 +72,3 @@ InstanceId: testInstanceId,

func fixtureRequest(mods ...func(request *mongodbflex.ApiGetBackupRequest)) mongodbflex.ApiGetBackupRequest {
request := testClient.GetBackup(testCtx, testProjectId, testInstanceId, testBackupId)
request := testClient.GetBackup(testCtx, testProjectId, testInstanceId, testBackupId, testRegion)
for _, mod := range mods {

@@ -113,3 +117,3 @@ mod(&request)

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, projectIdFlag)
delete(flagValues, globalflags.ProjectIdFlag)
}),

@@ -122,3 +126,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = ""
flagValues[globalflags.ProjectIdFlag] = ""
}),

@@ -131,3 +135,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = "invalid-uuid"
flagValues[globalflags.ProjectIdFlag] = "invalid-uuid"
}),

@@ -134,0 +138,0 @@ isValid: false,

@@ -64,3 +64,3 @@ package describe

instanceLabel, err := mongoUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId)
instanceLabel, err := mongoUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId, model.Region)
if err != nil {

@@ -79,3 +79,3 @@ params.Printer.Debug(print.ErrorLevel, "get instance name: %v", err)

restoreJobs, err := apiClient.ListRestoreJobs(ctx, model.ProjectId, model.InstanceId).Execute()
restoreJobs, err := apiClient.ListRestoreJobs(ctx, model.ProjectId, model.InstanceId, model.Region).Execute()
if err != nil {

@@ -127,3 +127,3 @@ return fmt.Errorf("get restore jobs for MongoDB Flex instance %q: %w", instanceLabel, err)

func buildRequest(ctx context.Context, model *inputModel, apiClient *mongodbflex.APIClient) mongodbflex.ApiGetBackupRequest {
req := apiClient.GetBackup(ctx, model.ProjectId, model.InstanceId, model.BackupId)
req := apiClient.GetBackup(ctx, model.ProjectId, model.InstanceId, model.BackupId, model.Region)
return req

@@ -130,0 +130,0 @@ }

@@ -18,3 +18,5 @@ package list

var projectIdFlag = globalflags.ProjectIdFlag
const (
testRegion = "eu02"
)

@@ -30,5 +32,6 @@ type testCtxKey struct{}

flagValues := map[string]string{
projectIdFlag: testProjectId,
instanceIdFlag: testInstanceId,
limitFlag: "10",
globalflags.ProjectIdFlag: testProjectId,
globalflags.RegionFlag: testRegion,
instanceIdFlag: testInstanceId,
limitFlag: "10",
}

@@ -46,2 +49,3 @@ for _, mod := range mods {

Verbosity: globalflags.VerbosityDefault,
Region: testRegion,
},

@@ -58,3 +62,3 @@ InstanceId: utils.Ptr(testInstanceId),

func fixtureRequest(mods ...func(request *mongodbflex.ApiListBackupsRequest)) mongodbflex.ApiListBackupsRequest {
request := testClient.ListBackups(testCtx, testProjectId, testInstanceId)
request := testClient.ListBackups(testCtx, testProjectId, testInstanceId, testRegion)
for _, mod := range mods {

@@ -87,3 +91,3 @@ mod(&request)

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, projectIdFlag)
delete(flagValues, globalflags.ProjectIdFlag)
}),

@@ -95,3 +99,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = ""
flagValues[globalflags.ProjectIdFlag] = ""
}),

@@ -103,3 +107,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = "invalid-uuid"
flagValues[globalflags.ProjectIdFlag] = "invalid-uuid"
}),

@@ -106,0 +110,0 @@ isValid: false,

@@ -67,3 +67,3 @@ package list

instanceLabel, err := mongodbflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, *model.InstanceId)
instanceLabel, err := mongodbflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, *model.InstanceId, model.Region)
if err != nil {

@@ -86,3 +86,3 @@ params.Printer.Debug(print.ErrorLevel, "get instance name: %v", err)

restoreJobs, err := apiClient.ListRestoreJobs(ctx, model.ProjectId, *model.InstanceId).Execute()
restoreJobs, err := apiClient.ListRestoreJobs(ctx, model.ProjectId, *model.InstanceId, model.Region).Execute()
if err != nil {

@@ -146,3 +146,3 @@ return fmt.Errorf("get restore jobs for MongoDB Flex instance %q: %w", instanceLabel, err)

func buildRequest(ctx context.Context, model *inputModel, apiClient *mongodbflex.APIClient) mongodbflex.ApiListBackupsRequest {
req := apiClient.ListBackups(ctx, model.ProjectId, *model.InstanceId)
req := apiClient.ListBackups(ctx, model.ProjectId, *model.InstanceId, model.Region)
return req

@@ -149,0 +149,0 @@ }

@@ -18,3 +18,5 @@ package restorejobs

var projectIdFlag = globalflags.ProjectIdFlag
const (
testRegion = "eu02"
)

@@ -30,5 +32,6 @@ type testCtxKey struct{}

flagValues := map[string]string{
projectIdFlag: testProjectId,
instanceIdFlag: testInstanceId,
limitFlag: "10",
globalflags.ProjectIdFlag: testProjectId,
globalflags.RegionFlag: testRegion,
instanceIdFlag: testInstanceId,
limitFlag: "10",
}

@@ -45,2 +48,3 @@ for _, mod := range mods {

ProjectId: testProjectId,
Region: testRegion,
Verbosity: globalflags.VerbosityDefault,

@@ -58,3 +62,3 @@ },

func fixtureRequest(mods ...func(request *mongodbflex.ApiListRestoreJobsRequest)) mongodbflex.ApiListRestoreJobsRequest {
request := testClient.ListRestoreJobs(testCtx, testProjectId, testInstanceId)
request := testClient.ListRestoreJobs(testCtx, testProjectId, testInstanceId, testRegion)
for _, mod := range mods {

@@ -87,3 +91,3 @@ mod(&request)

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, projectIdFlag)
delete(flagValues, globalflags.ProjectIdFlag)
}),

@@ -95,3 +99,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = ""
flagValues[globalflags.ProjectIdFlag] = ""
}),

@@ -103,3 +107,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = "invalid-uuid"
flagValues[globalflags.ProjectIdFlag] = "invalid-uuid"
}),

@@ -106,0 +110,0 @@ isValid: false,

@@ -66,3 +66,3 @@ package restorejobs

instanceLabel, err := mongodbflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, *model.InstanceId)
instanceLabel, err := mongodbflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, *model.InstanceId, model.Region)
if err != nil {

@@ -139,3 +139,3 @@ params.Printer.Debug(print.ErrorLevel, "get instance name: %v", err)

func buildRequest(ctx context.Context, model *inputModel, apiClient *mongodbflex.APIClient) mongodbflex.ApiListRestoreJobsRequest {
req := apiClient.ListRestoreJobs(ctx, model.ProjectId, *model.InstanceId)
req := apiClient.ListRestoreJobs(ctx, model.ProjectId, *model.InstanceId, model.Region)
return req

@@ -142,0 +142,0 @@ }

@@ -18,7 +18,6 @@ package restore

var projectIdFlag = globalflags.ProjectIdFlag
type testCtxKey struct{}
const (
testRegion = "eu02"
testBackupId = "backupID"

@@ -37,6 +36,7 @@ testTimestamp = "2021-01-01T00:00:00Z"

flagValues := map[string]string{
projectIdFlag: testProjectId,
backupIdFlag: testBackupId,
backupInstanceIdFlag: testBackupInstanceId,
instanceIdFlag: testInstanceId,
globalflags.ProjectIdFlag: testProjectId,
globalflags.RegionFlag: testRegion,
backupIdFlag: testBackupId,
backupInstanceIdFlag: testBackupInstanceId,
instanceIdFlag: testInstanceId,
}

@@ -53,2 +53,3 @@ for _, mod := range mods {

ProjectId: testProjectId,
Region: testRegion,
Verbosity: globalflags.VerbosityDefault,

@@ -67,3 +68,3 @@ },

func fixtureRestoreRequest(mods ...func(request mongodbflex.ApiRestoreInstanceRequest)) mongodbflex.ApiRestoreInstanceRequest {
request := testClient.RestoreInstance(testCtx, testProjectId, testInstanceId)
request := testClient.RestoreInstance(testCtx, testProjectId, testInstanceId, testRegion)
request = request.RestoreInstancePayload(mongodbflex.RestoreInstancePayload{

@@ -80,3 +81,3 @@ BackupId: utils.Ptr(testBackupId),

func fixtureCloneRequest(mods ...func(request mongodbflex.ApiCloneInstanceRequest)) mongodbflex.ApiCloneInstanceRequest {
request := testClient.CloneInstance(testCtx, testProjectId, testInstanceId)
request := testClient.CloneInstance(testCtx, testProjectId, testInstanceId, testRegion)
request = request.CloneInstancePayload(mongodbflex.CloneInstancePayload{

@@ -114,3 +115,3 @@ Timestamp: utils.Ptr(testTimestamp),

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, projectIdFlag)
delete(flagValues, globalflags.ProjectIdFlag)
}),

@@ -122,3 +123,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = ""
flagValues[globalflags.ProjectIdFlag] = ""
}),

@@ -130,3 +131,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = "invalid-uuid"
flagValues[globalflags.ProjectIdFlag] = "invalid-uuid"
}),

@@ -133,0 +134,0 @@ isValid: false,

@@ -73,3 +73,3 @@ package restore

instanceLabel, err := mongodbUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId)
instanceLabel, err := mongodbUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId, model.Region)
if err != nil {

@@ -106,3 +106,3 @@ params.Printer.Debug(print.ErrorLevel, "get instance name: %v", err)

s.Start("Restoring instance")
_, err = wait.RestoreInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId, model.BackupId).WaitWithContext(ctx)
_, err = wait.RestoreInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId, model.BackupId, model.Region).WaitWithContext(ctx)
if err != nil {

@@ -128,3 +128,3 @@ return fmt.Errorf("wait for MongoDB Flex instance restoration: %w", err)

s.Start("Cloning instance")
_, err = wait.CloneInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId).WaitWithContext(ctx)
_, err = wait.CloneInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId, model.Region).WaitWithContext(ctx)
if err != nil {

@@ -190,3 +190,3 @@ return fmt.Errorf("wait for MongoDB Flex instance cloning: %w", err)

func buildRestoreRequest(ctx context.Context, model *inputModel, apiClient *mongodbflex.APIClient) mongodbflex.ApiRestoreInstanceRequest {
req := apiClient.RestoreInstance(ctx, model.ProjectId, model.InstanceId)
req := apiClient.RestoreInstance(ctx, model.ProjectId, model.InstanceId, model.Region)
req = req.RestoreInstancePayload(mongodbflex.RestoreInstancePayload{

@@ -200,3 +200,3 @@ BackupId: &model.BackupId,

func buildCloneRequest(ctx context.Context, model *inputModel, apiClient *mongodbflex.APIClient) mongodbflex.ApiCloneInstanceRequest {
req := apiClient.CloneInstance(ctx, model.ProjectId, model.InstanceId)
req := apiClient.CloneInstance(ctx, model.ProjectId, model.InstanceId, model.Region)
req = req.CloneInstancePayload(mongodbflex.CloneInstancePayload{

@@ -203,0 +203,0 @@ Timestamp: &model.Timestamp,

@@ -18,3 +18,5 @@ package schedule

var projectIdFlag = globalflags.ProjectIdFlag
const (
testRegion = "eu02"
)

@@ -30,4 +32,5 @@ type testCtxKey struct{}

flagValues := map[string]string{
projectIdFlag: testProjectId,
instanceIdFlag: testInstanceId,
globalflags.ProjectIdFlag: testProjectId,
globalflags.RegionFlag: testRegion,
instanceIdFlag: testInstanceId,
}

@@ -44,2 +47,3 @@ for _, mod := range mods {

ProjectId: testProjectId,
Region: testRegion,
Verbosity: globalflags.VerbosityDefault,

@@ -56,3 +60,3 @@ },

func fixtureRequest(mods ...func(request *mongodbflex.ApiGetInstanceRequest)) mongodbflex.ApiGetInstanceRequest {
request := testClient.GetInstance(testCtx, testProjectId, testInstanceId)
request := testClient.GetInstance(testCtx, testProjectId, testInstanceId, testRegion)
for _, mod := range mods {

@@ -85,3 +89,3 @@ mod(&request)

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, projectIdFlag)
delete(flagValues, globalflags.ProjectIdFlag)
}),

@@ -93,3 +97,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = ""
flagValues[globalflags.ProjectIdFlag] = ""
}),

@@ -101,3 +105,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = "invalid-uuid"
flagValues[globalflags.ProjectIdFlag] = "invalid-uuid"
}),

@@ -104,0 +108,0 @@ isValid: false,

@@ -103,3 +103,3 @@ package schedule

func buildRequest(ctx context.Context, model *inputModel, apiClient *mongodbflex.APIClient) mongodbflex.ApiGetInstanceRequest {
req := apiClient.GetInstance(ctx, model.ProjectId, model.InstanceId)
req := apiClient.GetInstance(ctx, model.ProjectId, model.InstanceId, model.Region)
return req

@@ -106,0 +106,0 @@ }

@@ -16,3 +16,6 @@ package updateschedule

var projectIdFlag = globalflags.ProjectIdFlag
const (
testRegion = "eu02"
testSchedule = "0 0/6 * * *"
)

@@ -25,9 +28,9 @@ type testCtxKey struct{}

var testInstanceId = uuid.NewString()
var testSchedule = "0 0/6 * * *"
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
projectIdFlag: testProjectId,
scheduleFlag: testSchedule,
instanceIdFlag: testInstanceId,
globalflags.ProjectIdFlag: testProjectId,
globalflags.RegionFlag: testRegion,
scheduleFlag: testSchedule,
instanceIdFlag: testInstanceId,
}

@@ -44,6 +47,7 @@ for _, mod := range mods {

ProjectId: testProjectId,
Region: testRegion,
Verbosity: globalflags.VerbosityDefault,
},
InstanceId: utils.Ptr(testInstanceId),
BackupSchedule: &testSchedule,
BackupSchedule: utils.Ptr(testSchedule),
}

@@ -72,3 +76,3 @@ for _, mod := range mods {

func fixtureUpdateBackupScheduleRequest(mods ...func(request *mongodbflex.ApiUpdateBackupScheduleRequest)) mongodbflex.ApiUpdateBackupScheduleRequest {
request := testClient.UpdateBackupSchedule(testCtx, testProjectId, testInstanceId)
request := testClient.UpdateBackupSchedule(testCtx, testProjectId, testInstanceId, testRegion)
request = request.UpdateBackupSchedulePayload(fixturePayload())

@@ -82,3 +86,3 @@ for _, mod := range mods {

func fixtureGetInstanceRequest(mods ...func(request *mongodbflex.ApiGetInstanceRequest)) mongodbflex.ApiGetInstanceRequest {
request := testClient.GetInstance(testCtx, testProjectId, testInstanceId)
request := testClient.GetInstance(testCtx, testProjectId, testInstanceId, testRegion)
for _, mod := range mods {

@@ -92,3 +96,3 @@ mod(&request)

instance := mongodbflex.Instance{
BackupSchedule: &testSchedule,
BackupSchedule: utils.Ptr(testSchedule),
Options: &map[string]string{

@@ -130,3 +134,3 @@ "dailySnapshotRetentionDays": "0",

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, projectIdFlag)
delete(flagValues, globalflags.ProjectIdFlag)
}),

@@ -138,3 +142,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = ""
flagValues[globalflags.ProjectIdFlag] = ""
}),

@@ -146,3 +150,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = "invalid-uuid"
flagValues[globalflags.ProjectIdFlag] = "invalid-uuid"
}),

@@ -272,2 +276,3 @@ isValid: false,

ProjectId: testProjectId,
Region: testRegion,
},

@@ -289,2 +294,3 @@ InstanceId: utils.Ptr(testInstanceId),

ProjectId: testProjectId,
Region: testRegion,
},

@@ -314,2 +320,3 @@ InstanceId: utils.Ptr(testInstanceId),

ProjectId: testProjectId,
Region: testRegion,
},

@@ -316,0 +323,0 @@ InstanceId: utils.Ptr(testInstanceId),

@@ -84,3 +84,3 @@ package updateschedule

instanceLabel, err := mongoDBflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, *model.InstanceId)
instanceLabel, err := mongoDBflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, *model.InstanceId, model.Region)
if err != nil {

@@ -163,3 +163,3 @@ params.Printer.Debug(print.ErrorLevel, "get instance name: %v", err)

func buildUpdateBackupScheduleRequest(ctx context.Context, model *inputModel, instance *mongodbflex.Instance, apiClient *mongodbflex.APIClient) mongodbflex.ApiUpdateBackupScheduleRequest {
req := apiClient.UpdateBackupSchedule(ctx, model.ProjectId, *model.InstanceId)
req := apiClient.UpdateBackupSchedule(ctx, model.ProjectId, *model.InstanceId, model.Region)

@@ -234,4 +234,4 @@ payload := getUpdateBackupSchedulePayload(instance)

func buildGetInstanceRequest(ctx context.Context, model *inputModel, apiClient *mongodbflex.APIClient) mongodbflex.ApiGetInstanceRequest {
req := apiClient.GetInstance(ctx, model.ProjectId, *model.InstanceId)
req := apiClient.GetInstance(ctx, model.ProjectId, *model.InstanceId, model.Region)
return req
}

@@ -19,3 +19,5 @@ package create

var projectIdFlag = globalflags.ProjectIdFlag
const (
testRegion = "eu02"
)

@@ -34,7 +36,7 @@ type testCtxKey struct{}

func (c *mongoDBFlexClientMocked) CreateInstance(ctx context.Context, projectId string) mongodbflex.ApiCreateInstanceRequest {
return testClient.CreateInstance(ctx, projectId)
func (c *mongoDBFlexClientMocked) CreateInstance(ctx context.Context, projectId, region string) mongodbflex.ApiCreateInstanceRequest {
return testClient.CreateInstance(ctx, projectId, region)
}
func (c *mongoDBFlexClientMocked) ListStoragesExecute(_ context.Context, _, _ string) (*mongodbflex.ListStoragesResponse, error) {
func (c *mongoDBFlexClientMocked) ListStoragesExecute(_ context.Context, _, _, _ string) (*mongodbflex.ListStoragesResponse, error) {
if c.listFlavorsFails {

@@ -46,3 +48,3 @@ return nil, fmt.Errorf("list storages failed")

func (c *mongoDBFlexClientMocked) ListFlavorsExecute(_ context.Context, _ string) (*mongodbflex.ListFlavorsResponse, error) {
func (c *mongoDBFlexClientMocked) ListFlavorsExecute(_ context.Context, _, _ string) (*mongodbflex.ListFlavorsResponse, error) {
if c.listFlavorsFails {

@@ -59,11 +61,12 @@ return nil, fmt.Errorf("list flavors failed")

flagValues := map[string]string{
projectIdFlag: testProjectId,
instanceNameFlag: "example-name",
aclFlag: "0.0.0.0/0",
backupScheduleFlag: "0 0/6 * * *",
flavorIdFlag: testFlavorId,
storageClassFlag: "premium-perf4-mongodb", // Non-default
storageSizeFlag: "10",
versionFlag: "6.0",
typeFlag: "Replica",
globalflags.ProjectIdFlag: testProjectId,
globalflags.RegionFlag: testRegion,
instanceNameFlag: "example-name",
aclFlag: "0.0.0.0/0",
backupScheduleFlag: "0 0/6 * * *",
flavorIdFlag: testFlavorId,
storageClassFlag: "premium-perf4-mongodb", // Non-default
storageSizeFlag: "10",
versionFlag: "6.0",
typeFlag: "Replica",
}

@@ -80,2 +83,3 @@ for _, mod := range mods {

ProjectId: testProjectId,
Region: testRegion,
Verbosity: globalflags.VerbosityDefault,

@@ -99,3 +103,3 @@ },

func fixtureRequest(mods ...func(request *mongodbflex.ApiCreateInstanceRequest)) mongodbflex.ApiCreateInstanceRequest {
request := testClient.CreateInstance(testCtx, testProjectId)
request := testClient.CreateInstance(testCtx, testProjectId, testRegion)
request = request.CreateInstancePayload(fixturePayload())

@@ -111,3 +115,3 @@ for _, mod := range mods {

Name: utils.Ptr("example-name"),
Acl: &mongodbflex.ACL{Items: utils.Ptr([]string{"0.0.0.0/0"})},
Acl: &mongodbflex.CreateInstancePayloadAcl{Items: utils.Ptr([]string{"0.0.0.0/0"})},
BackupSchedule: utils.Ptr("0 0/6 * * *"),

@@ -176,3 +180,3 @@ FlavorId: utils.Ptr(testFlavorId),

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, projectIdFlag)
delete(flagValues, globalflags.ProjectIdFlag)
}),

@@ -184,3 +188,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = ""
flagValues[globalflags.ProjectIdFlag] = ""
}),

@@ -192,3 +196,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = "invalid-uuid"
flagValues[globalflags.ProjectIdFlag] = "invalid-uuid"
}),

@@ -334,3 +338,3 @@ isValid: false,

listFlavorsResp: &mongodbflex.ListFlavorsResponse{
Flavors: &[]mongodbflex.HandlersInfraFlavor{
Flavors: &[]mongodbflex.InstanceFlavor{
{

@@ -363,3 +367,3 @@ Id: utils.Ptr(testFlavorId),

listFlavorsResp: &mongodbflex.ListFlavorsResponse{
Flavors: &[]mongodbflex.HandlersInfraFlavor{
Flavors: &[]mongodbflex.InstanceFlavor{
{

@@ -394,3 +398,3 @@ Id: utils.Ptr(testFlavorId),

listFlavorsResp: &mongodbflex.ListFlavorsResponse{
Flavors: &[]mongodbflex.HandlersInfraFlavor{
Flavors: &[]mongodbflex.InstanceFlavor{
{

@@ -420,3 +424,3 @@ Id: utils.Ptr(testFlavorId),

listFlavorsResp: &mongodbflex.ListFlavorsResponse{
Flavors: &[]mongodbflex.HandlersInfraFlavor{
Flavors: &[]mongodbflex.InstanceFlavor{
{

@@ -459,3 +463,3 @@ Id: utils.Ptr(testFlavorId),

listFlavorsResp: &mongodbflex.ListFlavorsResponse{
Flavors: &[]mongodbflex.HandlersInfraFlavor{
Flavors: &[]mongodbflex.InstanceFlavor{
{

@@ -495,3 +499,3 @@ Id: utils.Ptr(testFlavorId),

listFlavorsResp: &mongodbflex.ListFlavorsResponse{
Flavors: &[]mongodbflex.HandlersInfraFlavor{
Flavors: &[]mongodbflex.InstanceFlavor{
{

@@ -521,3 +525,3 @@ Id: utils.Ptr(testFlavorId),

listFlavorsResp: &mongodbflex.ListFlavorsResponse{
Flavors: &[]mongodbflex.HandlersInfraFlavor{
Flavors: &[]mongodbflex.InstanceFlavor{
{

@@ -524,0 +528,0 @@ Id: utils.Ptr(testFlavorId),

@@ -107,3 +107,3 @@ package create

if model.Version == nil {
version, err := mongodbflexUtils.GetLatestMongoDBVersion(ctx, apiClient, model.ProjectId)
version, err := mongodbflexUtils.GetLatestMongoDBVersion(ctx, apiClient, model.ProjectId, model.Region)
if err != nil {

@@ -130,3 +130,3 @@ return fmt.Errorf("get latest MongoDB version: %w", err)

s.Start("Creating instance")
_, err = wait.CreateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId).WaitWithContext(ctx)
_, err = wait.CreateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId, model.Region).WaitWithContext(ctx)
if err != nil {

@@ -213,9 +213,9 @@ return fmt.Errorf("wait for MongoDB Flex instance creation: %w", err)

type MongoDBFlexClient interface {
CreateInstance(ctx context.Context, projectId string) mongodbflex.ApiCreateInstanceRequest
ListFlavorsExecute(ctx context.Context, projectId string) (*mongodbflex.ListFlavorsResponse, error)
ListStoragesExecute(ctx context.Context, projectId, flavorId string) (*mongodbflex.ListStoragesResponse, error)
CreateInstance(ctx context.Context, projectId, region string) mongodbflex.ApiCreateInstanceRequest
ListFlavorsExecute(ctx context.Context, projectId, region string) (*mongodbflex.ListFlavorsResponse, error)
ListStoragesExecute(ctx context.Context, projectId, flavorId, region string) (*mongodbflex.ListStoragesResponse, error)
}
func buildRequest(ctx context.Context, model *inputModel, apiClient MongoDBFlexClient) (mongodbflex.ApiCreateInstanceRequest, error) {
req := apiClient.CreateInstance(ctx, model.ProjectId)
req := apiClient.CreateInstance(ctx, model.ProjectId, model.Region)

@@ -225,3 +225,3 @@ var flavorId *string

flavors, err := apiClient.ListFlavorsExecute(ctx, model.ProjectId)
flavors, err := apiClient.ListFlavorsExecute(ctx, model.ProjectId, model.Region)
if err != nil {

@@ -248,3 +248,3 @@ return req, fmt.Errorf("get MongoDB Flex flavors: %w", err)

storages, err := apiClient.ListStoragesExecute(ctx, model.ProjectId, *flavorId)
storages, err := apiClient.ListStoragesExecute(ctx, model.ProjectId, *flavorId, model.Region)
if err != nil {

@@ -265,3 +265,3 @@ return req, fmt.Errorf("get MongoDB Flex storages: %w", err)

Name: model.InstanceName,
Acl: &mongodbflex.ACL{Items: model.ACL},
Acl: &mongodbflex.CreateInstancePayloadAcl{Items: model.ACL},
BackupSchedule: model.BackupSchedule,

@@ -268,0 +268,0 @@ FlavorId: flavorId,

@@ -17,3 +17,5 @@ package delete

var projectIdFlag = globalflags.ProjectIdFlag
const (
testRegion = "eu02"
)

@@ -39,3 +41,4 @@ type testCtxKey struct{}

flagValues := map[string]string{
projectIdFlag: testProjectId,
globalflags.ProjectIdFlag: testProjectId,
globalflags.RegionFlag: testRegion,
}

@@ -52,2 +55,3 @@ for _, mod := range mods {

ProjectId: testProjectId,
Region: testRegion,
Verbosity: globalflags.VerbosityDefault,

@@ -64,3 +68,3 @@ },

func fixtureRequest(mods ...func(request *mongodbflex.ApiDeleteInstanceRequest)) mongodbflex.ApiDeleteInstanceRequest {
request := testClient.DeleteInstance(testCtx, testProjectId, testInstanceId)
request := testClient.DeleteInstance(testCtx, testProjectId, testInstanceId, testRegion)
for _, mod := range mods {

@@ -109,3 +113,3 @@ mod(&request)

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, projectIdFlag)
delete(flagValues, globalflags.ProjectIdFlag)
}),

@@ -118,3 +122,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = ""
flagValues[globalflags.ProjectIdFlag] = ""
}),

@@ -127,3 +131,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = "invalid-uuid"
flagValues[globalflags.ProjectIdFlag] = "invalid-uuid"
}),

@@ -130,0 +134,0 @@ isValid: false,

@@ -56,3 +56,3 @@ package delete

instanceLabel, err := mongodbflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId)
instanceLabel, err := mongodbflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId, model.Region)
if err != nil {

@@ -82,3 +82,3 @@ params.Printer.Debug(print.ErrorLevel, "get instance name: %v", err)

s.Start("Deleting instance")
_, err = wait.DeleteInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId).WaitWithContext(ctx)
_, err = wait.DeleteInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId, model.Region).WaitWithContext(ctx)
if err != nil {

@@ -127,4 +127,4 @@ return fmt.Errorf("wait for MongoDB Flex instance deletion: %w", err)

func buildRequest(ctx context.Context, model *inputModel, apiClient *mongodbflex.APIClient) mongodbflex.ApiDeleteInstanceRequest {
req := apiClient.DeleteInstance(ctx, model.ProjectId, model.InstanceId)
req := apiClient.DeleteInstance(ctx, model.ProjectId, model.InstanceId, model.Region)
return req
}

@@ -17,3 +17,5 @@ package describe

var projectIdFlag = globalflags.ProjectIdFlag
const (
testRegion = "eu02"
)

@@ -39,3 +41,4 @@ type testCtxKey struct{}

flagValues := map[string]string{
projectIdFlag: testProjectId,
globalflags.ProjectIdFlag: testProjectId,
globalflags.RegionFlag: testRegion,
}

@@ -52,2 +55,3 @@ for _, mod := range mods {

ProjectId: testProjectId,
Region: testRegion,
Verbosity: globalflags.VerbosityDefault,

@@ -64,3 +68,3 @@ },

func fixtureRequest(mods ...func(request *mongodbflex.ApiGetInstanceRequest)) mongodbflex.ApiGetInstanceRequest {
request := testClient.GetInstance(testCtx, testProjectId, testInstanceId)
request := testClient.GetInstance(testCtx, testProjectId, testInstanceId, testRegion)
for _, mod := range mods {

@@ -109,3 +113,3 @@ mod(&request)

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, projectIdFlag)
delete(flagValues, globalflags.ProjectIdFlag)
}),

@@ -118,3 +122,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = ""
flagValues[globalflags.ProjectIdFlag] = ""
}),

@@ -127,3 +131,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = "invalid-uuid"
flagValues[globalflags.ProjectIdFlag] = "invalid-uuid"
}),

@@ -130,0 +134,0 @@ isValid: false,

@@ -99,3 +99,3 @@ package describe

func buildRequest(ctx context.Context, model *inputModel, apiClient *mongodbflex.APIClient) mongodbflex.ApiGetInstanceRequest {
req := apiClient.GetInstance(ctx, model.ProjectId, model.InstanceId)
req := apiClient.GetInstance(ctx, model.ProjectId, model.InstanceId, model.Region)
return req

@@ -102,0 +102,0 @@ }

@@ -19,3 +19,5 @@ package list

var projectIdFlag = globalflags.ProjectIdFlag
const (
testRegion = "eu02"
)

@@ -30,4 +32,5 @@ type testCtxKey struct{}

flagValues := map[string]string{
projectIdFlag: testProjectId,
limitFlag: "10",
globalflags.ProjectIdFlag: testProjectId,
globalflags.RegionFlag: testRegion,
limitFlag: "10",
}

@@ -44,2 +47,3 @@ for _, mod := range mods {

ProjectId: testProjectId,
Region: testRegion,
Verbosity: globalflags.VerbosityDefault,

@@ -56,3 +60,3 @@ },

func fixtureRequest(mods ...func(request *mongodbflex.ApiListInstancesRequest)) mongodbflex.ApiListInstancesRequest {
request := testClient.ListInstances(testCtx, testProjectId).Tag("")
request := testClient.ListInstances(testCtx, testProjectId, testRegion).Tag("")
for _, mod := range mods {

@@ -85,3 +89,3 @@ mod(&request)

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, projectIdFlag)
delete(flagValues, globalflags.ProjectIdFlag)
}),

@@ -93,3 +97,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = ""
flagValues[globalflags.ProjectIdFlag] = ""
}),

@@ -101,3 +105,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = "invalid-uuid"
flagValues[globalflags.ProjectIdFlag] = "invalid-uuid"
}),

@@ -104,0 +108,0 @@ isValid: false,

@@ -130,3 +130,3 @@ package list

func buildRequest(ctx context.Context, model *inputModel, apiClient *mongodbflex.APIClient) mongodbflex.ApiListInstancesRequest {
req := apiClient.ListInstances(ctx, model.ProjectId).Tag("")
req := apiClient.ListInstances(ctx, model.ProjectId, model.Region).Tag("")
return req

@@ -133,0 +133,0 @@ }

@@ -19,3 +19,5 @@ package update

var projectIdFlag = globalflags.ProjectIdFlag
const (
testRegion = "eu02"
)

@@ -33,10 +35,10 @@ type testCtxKey struct{}

getInstanceFails bool
getInstanceResp *mongodbflex.GetInstanceResponse
getInstanceResp *mongodbflex.InstanceResponse
}
func (c *mongoDBFlexClientMocked) PartialUpdateInstance(ctx context.Context, projectId, instanceId string) mongodbflex.ApiPartialUpdateInstanceRequest {
return testClient.PartialUpdateInstance(ctx, projectId, instanceId)
func (c *mongoDBFlexClientMocked) PartialUpdateInstance(ctx context.Context, projectId, instanceId, region string) mongodbflex.ApiPartialUpdateInstanceRequest {
return testClient.PartialUpdateInstance(ctx, projectId, instanceId, region)
}
func (c *mongoDBFlexClientMocked) GetInstanceExecute(_ context.Context, _, _ string) (*mongodbflex.GetInstanceResponse, error) {
func (c *mongoDBFlexClientMocked) GetInstanceExecute(_ context.Context, _, _, _ string) (*mongodbflex.InstanceResponse, error) {
if c.getInstanceFails {

@@ -48,3 +50,3 @@ return nil, fmt.Errorf("get instance failed")

func (c *mongoDBFlexClientMocked) ListStoragesExecute(_ context.Context, _, _ string) (*mongodbflex.ListStoragesResponse, error) {
func (c *mongoDBFlexClientMocked) ListStoragesExecute(_ context.Context, _, _, _ string) (*mongodbflex.ListStoragesResponse, error) {
if c.listFlavorsFails {

@@ -56,3 +58,3 @@ return nil, fmt.Errorf("list storages failed")

func (c *mongoDBFlexClientMocked) ListFlavorsExecute(_ context.Context, _ string) (*mongodbflex.ListFlavorsResponse, error) {
func (c *mongoDBFlexClientMocked) ListFlavorsExecute(_ context.Context, _, _ string) (*mongodbflex.ListFlavorsResponse, error) {
if c.listFlavorsFails {

@@ -80,3 +82,4 @@ return nil, fmt.Errorf("list flavors failed")

flagValues := map[string]string{
projectIdFlag: testProjectId,
globalflags.ProjectIdFlag: testProjectId,
globalflags.RegionFlag: testRegion,
}

@@ -91,11 +94,12 @@ for _, mod := range mods {

flagValues := map[string]string{
projectIdFlag: testProjectId,
flavorIdFlag: testFlavorId,
instanceNameFlag: "example-name",
aclFlag: "0.0.0.0/0",
backupScheduleFlag: "0 0 * * *",
storageClassFlag: "class",
storageSizeFlag: "10",
versionFlag: "5.0",
typeFlag: "Single",
globalflags.ProjectIdFlag: testProjectId,
globalflags.RegionFlag: testRegion,
flavorIdFlag: testFlavorId,
instanceNameFlag: "example-name",
aclFlag: "0.0.0.0/0",
backupScheduleFlag: "0 0 * * *",
storageClassFlag: "class",
storageSizeFlag: "10",
versionFlag: "5.0",
typeFlag: "Single",
}

@@ -112,2 +116,3 @@ for _, mod := range mods {

ProjectId: testProjectId,
Region: testRegion,
Verbosity: globalflags.VerbosityDefault,

@@ -127,2 +132,3 @@ },

ProjectId: testProjectId,
Region: testRegion,
Verbosity: globalflags.VerbosityDefault,

@@ -147,3 +153,3 @@ },

func fixtureRequest(mods ...func(request *mongodbflex.ApiPartialUpdateInstanceRequest)) mongodbflex.ApiPartialUpdateInstanceRequest {
request := testClient.PartialUpdateInstance(testCtx, testProjectId, testInstanceId)
request := testClient.PartialUpdateInstance(testCtx, testProjectId, testInstanceId, testRegion)
request = request.PartialUpdateInstancePayload(mongodbflex.PartialUpdateInstancePayload{})

@@ -216,3 +222,3 @@ for _, mod := range mods {

flagValues: fixtureRequiredFlagValues(func(flagValues map[string]string) {
delete(flagValues, projectIdFlag)
delete(flagValues, globalflags.ProjectIdFlag)
}),

@@ -225,3 +231,3 @@ isValid: false,

flagValues: fixtureRequiredFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = ""
flagValues[globalflags.ProjectIdFlag] = ""
}),

@@ -234,3 +240,3 @@ isValid: false,

flagValues: fixtureRequiredFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = "invalid-uuid"
flagValues[globalflags.ProjectIdFlag] = "invalid-uuid"
}),

@@ -363,3 +369,3 @@ isValid: false,

getInstanceFails bool
getInstanceResp *mongodbflex.GetInstanceResponse
getInstanceResp *mongodbflex.InstanceResponse
listFlavorsFails bool

@@ -384,3 +390,3 @@ listFlavorsResp *mongodbflex.ListFlavorsResponse

listFlavorsResp: &mongodbflex.ListFlavorsResponse{
Flavors: &[]mongodbflex.HandlersInfraFlavor{
Flavors: &[]mongodbflex.InstanceFlavor{
{

@@ -393,3 +399,3 @@ Id: utils.Ptr(testFlavorId),

},
expectedRequest: testClient.PartialUpdateInstance(testCtx, testProjectId, testInstanceId).
expectedRequest: testClient.PartialUpdateInstance(testCtx, testProjectId, testInstanceId, testRegion).
PartialUpdateInstancePayload(mongodbflex.PartialUpdateInstancePayload{

@@ -407,3 +413,3 @@ FlavorId: utils.Ptr(testFlavorId),

listFlavorsResp: &mongodbflex.ListFlavorsResponse{
Flavors: &[]mongodbflex.HandlersInfraFlavor{
Flavors: &[]mongodbflex.InstanceFlavor{
{

@@ -416,3 +422,3 @@ Id: utils.Ptr(testFlavorId),

},
expectedRequest: testClient.PartialUpdateInstance(testCtx, testProjectId, testInstanceId).
expectedRequest: testClient.PartialUpdateInstance(testCtx, testProjectId, testInstanceId, testRegion).
PartialUpdateInstancePayload(mongodbflex.PartialUpdateInstancePayload{

@@ -428,3 +434,3 @@ FlavorId: utils.Ptr(testFlavorId),

isValid: true,
getInstanceResp: &mongodbflex.GetInstanceResponse{
getInstanceResp: &mongodbflex.InstanceResponse{
Item: &mongodbflex.Instance{

@@ -443,3 +449,3 @@ Flavor: &mongodbflex.Flavor{

},
expectedRequest: testClient.PartialUpdateInstance(testCtx, testProjectId, testInstanceId).
expectedRequest: testClient.PartialUpdateInstance(testCtx, testProjectId, testInstanceId, testRegion).
PartialUpdateInstancePayload(mongodbflex.PartialUpdateInstancePayload{

@@ -458,3 +464,3 @@ Storage: &mongodbflex.Storage{

isValid: true,
getInstanceResp: &mongodbflex.GetInstanceResponse{
getInstanceResp: &mongodbflex.InstanceResponse{
Item: &mongodbflex.Instance{

@@ -473,3 +479,3 @@ Flavor: &mongodbflex.Flavor{

},
expectedRequest: testClient.PartialUpdateInstance(testCtx, testProjectId, testInstanceId).
expectedRequest: testClient.PartialUpdateInstance(testCtx, testProjectId, testInstanceId, testRegion).
PartialUpdateInstancePayload(mongodbflex.PartialUpdateInstancePayload{

@@ -502,3 +508,3 @@ Storage: &mongodbflex.Storage{

listFlavorsResp: &mongodbflex.ListFlavorsResponse{
Flavors: &[]mongodbflex.HandlersInfraFlavor{
Flavors: &[]mongodbflex.InstanceFlavor{
{

@@ -547,3 +553,3 @@ Id: utils.Ptr(testFlavorId),

),
getInstanceResp: &mongodbflex.GetInstanceResponse{
getInstanceResp: &mongodbflex.InstanceResponse{
Item: &mongodbflex.Instance{

@@ -571,3 +577,3 @@ Flavor: &mongodbflex.Flavor{

),
getInstanceResp: &mongodbflex.GetInstanceResponse{
getInstanceResp: &mongodbflex.InstanceResponse{
Item: &mongodbflex.Instance{

@@ -574,0 +580,0 @@ Flavor: &mongodbflex.Flavor{

@@ -86,3 +86,3 @@ package update

instanceLabel, err := mongodbflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId)
instanceLabel, err := mongodbflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId, model.Region)
if err != nil {

@@ -116,3 +116,3 @@ params.Printer.Debug(print.ErrorLevel, "get instance name: %v", err)

s.Start("Updating instance")
_, err = wait.PartialUpdateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId).WaitWithContext(ctx)
_, err = wait.PartialUpdateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId, model.Region).WaitWithContext(ctx)
if err != nil {

@@ -205,10 +205,10 @@ return fmt.Errorf("wait for MongoDB Flex instance update: %w", err)

type MongoDBFlexClient interface {
PartialUpdateInstance(ctx context.Context, projectId, instanceId string) mongodbflex.ApiPartialUpdateInstanceRequest
GetInstanceExecute(ctx context.Context, projectId, instanceId string) (*mongodbflex.GetInstanceResponse, error)
ListFlavorsExecute(ctx context.Context, projectId string) (*mongodbflex.ListFlavorsResponse, error)
ListStoragesExecute(ctx context.Context, projectId, flavorId string) (*mongodbflex.ListStoragesResponse, error)
PartialUpdateInstance(ctx context.Context, projectId, instanceId, region string) mongodbflex.ApiPartialUpdateInstanceRequest
GetInstanceExecute(ctx context.Context, projectId, instanceId, region string) (*mongodbflex.InstanceResponse, error)
ListFlavorsExecute(ctx context.Context, projectId, region string) (*mongodbflex.ListFlavorsResponse, error)
ListStoragesExecute(ctx context.Context, projectId, flavorId, region string) (*mongodbflex.ListStoragesResponse, error)
}
func buildRequest(ctx context.Context, model *inputModel, apiClient MongoDBFlexClient) (mongodbflex.ApiPartialUpdateInstanceRequest, error) {
req := apiClient.PartialUpdateInstance(ctx, model.ProjectId, model.InstanceId)
req := apiClient.PartialUpdateInstance(ctx, model.ProjectId, model.InstanceId, model.Region)

@@ -218,3 +218,3 @@ var flavorId *string

flavors, err := apiClient.ListFlavorsExecute(ctx, model.ProjectId)
flavors, err := apiClient.ListFlavorsExecute(ctx, model.ProjectId, model.Region)
if err != nil {

@@ -228,3 +228,3 @@ return req, fmt.Errorf("get MongoDB Flex flavors: %w", err)

if model.RAM == nil || model.CPU == nil {
currentInstance, err := apiClient.GetInstanceExecute(ctx, model.ProjectId, model.InstanceId)
currentInstance, err := apiClient.GetInstanceExecute(ctx, model.ProjectId, model.InstanceId, model.Region)
if err != nil {

@@ -260,3 +260,3 @@ return req, fmt.Errorf("get MongoDB Flex instance: %w", err)

if validationFlavorId == nil {
currentInstance, err := apiClient.GetInstanceExecute(ctx, model.ProjectId, model.InstanceId)
currentInstance, err := apiClient.GetInstanceExecute(ctx, model.ProjectId, model.InstanceId, model.Region)
if err != nil {

@@ -267,3 +267,3 @@ return req, fmt.Errorf("get MongoDB Flex instance: %w", err)

}
storages, err = apiClient.ListStoragesExecute(ctx, model.ProjectId, *validationFlavorId)
storages, err = apiClient.ListStoragesExecute(ctx, model.ProjectId, *validationFlavorId, model.Region)
if err != nil {

@@ -270,0 +270,0 @@ return req, fmt.Errorf("get MongoDB Flex storages: %w", err)

@@ -31,3 +31,3 @@ package options

func (c *mongoDBFlexClientMocked) ListFlavorsExecute(_ context.Context, _ string) (*mongodbflex.ListFlavorsResponse, error) {
func (c *mongoDBFlexClientMocked) ListFlavorsExecute(_ context.Context, _, _ string) (*mongodbflex.ListFlavorsResponse, error) {
c.listFlavorsCalled = true

@@ -38,7 +38,7 @@ if c.listFlavorsFails {

return utils.Ptr(mongodbflex.ListFlavorsResponse{
Flavors: utils.Ptr([]mongodbflex.HandlersInfraFlavor{}),
Flavors: utils.Ptr([]mongodbflex.InstanceFlavor{}),
}), nil
}
func (c *mongoDBFlexClientMocked) ListVersionsExecute(_ context.Context, _ string) (*mongodbflex.ListVersionsResponse, error) {
func (c *mongoDBFlexClientMocked) ListVersionsExecute(_ context.Context, _, _ string) (*mongodbflex.ListVersionsResponse, error) {
c.listVersionsCalled = true

@@ -53,3 +53,3 @@ if c.listVersionsFails {

func (c *mongoDBFlexClientMocked) ListStoragesExecute(_ context.Context, _, _ string) (*mongodbflex.ListStoragesResponse, error) {
func (c *mongoDBFlexClientMocked) ListStoragesExecute(_ context.Context, _, _, _ string) (*mongodbflex.ListStoragesResponse, error) {
c.listStoragesCalled = true

@@ -56,0 +56,0 @@ if c.listStoragesFails {

@@ -40,5 +40,5 @@ package options

type options struct {
Flavors *[]mongodbflex.HandlersInfraFlavor `json:"flavors,omitempty"`
Versions *[]string `json:"versions,omitempty"`
Storages *flavorStorages `json:"flavorStorages,omitempty"`
Flavors *[]mongodbflex.InstanceFlavor `json:"flavors,omitempty"`
Versions *[]string `json:"versions,omitempty"`
Storages *flavorStorages `json:"flavorStorages,omitempty"`
}

@@ -142,5 +142,5 @@

type mongoDBFlexOptionsClient interface {
ListFlavorsExecute(ctx context.Context, projectId string) (*mongodbflex.ListFlavorsResponse, error)
ListVersionsExecute(ctx context.Context, projectId string) (*mongodbflex.ListVersionsResponse, error)
ListStoragesExecute(ctx context.Context, projectId, flavorId string) (*mongodbflex.ListStoragesResponse, error)
ListFlavorsExecute(ctx context.Context, projectId, region string) (*mongodbflex.ListFlavorsResponse, error)
ListVersionsExecute(ctx context.Context, projectId, region string) (*mongodbflex.ListVersionsResponse, error)
ListStoragesExecute(ctx context.Context, projectId, flavorId, region string) (*mongodbflex.ListStoragesResponse, error)
}

@@ -155,3 +155,3 @@

if model.Flavors {
flavors, err = apiClient.ListFlavorsExecute(ctx, model.ProjectId)
flavors, err = apiClient.ListFlavorsExecute(ctx, model.ProjectId, model.Region)
if err != nil {

@@ -162,3 +162,3 @@ return fmt.Errorf("get MongoDB Flex flavors: %w", err)

if model.Versions {
versions, err = apiClient.ListVersionsExecute(ctx, model.ProjectId)
versions, err = apiClient.ListVersionsExecute(ctx, model.ProjectId, model.Region)
if err != nil {

@@ -169,3 +169,3 @@ return fmt.Errorf("get MongoDB Flex versions: %w", err)

if model.Storages {
storages, err = apiClient.ListStoragesExecute(ctx, model.ProjectId, *model.FlavorId)
storages, err = apiClient.ListStoragesExecute(ctx, model.ProjectId, *model.FlavorId, model.Region)
if err != nil {

@@ -245,3 +245,3 @@ return fmt.Errorf("get MongoDB Flex storages: %w", err)

func buildFlavorsTable(flavors []mongodbflex.HandlersInfraFlavor) tables.Table {
func buildFlavorsTable(flavors []mongodbflex.InstanceFlavor) tables.Table {
table := tables.NewTable()

@@ -248,0 +248,0 @@ table.SetTitle("Flavors")

@@ -19,3 +19,5 @@ package create

var projectIdFlag = globalflags.ProjectIdFlag
const (
testRegion = "eu02"
)

@@ -31,7 +33,8 @@ type testCtxKey struct{}

flagValues := map[string]string{
projectIdFlag: testProjectId,
instanceIdFlag: testInstanceId,
usernameFlag: "johndoe",
databaseFlag: "default",
roleFlag: "read",
globalflags.ProjectIdFlag: testProjectId,
globalflags.RegionFlag: testRegion,
instanceIdFlag: testInstanceId,
usernameFlag: "johndoe",
databaseFlag: "default",
roleFlag: "read",
}

@@ -48,2 +51,3 @@ for _, mod := range mods {

ProjectId: testProjectId,
Region: testRegion,
Verbosity: globalflags.VerbosityDefault,

@@ -63,3 +67,3 @@ },

func fixtureRequest(mods ...func(request *mongodbflex.ApiCreateUserRequest)) mongodbflex.ApiCreateUserRequest {
request := testClient.CreateUser(testCtx, testProjectId, testInstanceId)
request := testClient.CreateUser(testCtx, testProjectId, testInstanceId, testRegion)
request = request.CreateUserPayload(mongodbflex.CreateUserPayload{

@@ -109,3 +113,3 @@ Username: utils.Ptr("johndoe"),

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, projectIdFlag)
delete(flagValues, globalflags.ProjectIdFlag)
}),

@@ -117,3 +121,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = ""
flagValues[globalflags.ProjectIdFlag] = ""
}),

@@ -125,3 +129,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = "invalid-uuid"
flagValues[globalflags.ProjectIdFlag] = "invalid-uuid"
}),

@@ -128,0 +132,0 @@ isValid: false,

@@ -75,3 +75,3 @@ package create

instanceLabel, err := mongodbflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId)
instanceLabel, err := mongodbflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId, model.Region)
if err != nil {

@@ -107,3 +107,3 @@ params.Printer.Debug(print.ErrorLevel, "get instance name: %v", err)

func configureFlags(cmd *cobra.Command) {
roleOptions := []string{"read", "readWrite"}
roleOptions := []string{"read", "readWrite", "readWriteAnyDatabase"}

@@ -146,3 +146,3 @@ cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "ID of the instance")

func buildRequest(ctx context.Context, model *inputModel, apiClient *mongodbflex.APIClient) mongodbflex.ApiCreateUserRequest {
req := apiClient.CreateUser(ctx, model.ProjectId, model.InstanceId)
req := apiClient.CreateUser(ctx, model.ProjectId, model.InstanceId, model.Region)
req = req.CreateUserPayload(mongodbflex.CreateUserPayload{

@@ -149,0 +149,0 @@ Username: model.Username,

@@ -17,3 +17,5 @@ package delete

var projectIdFlag = globalflags.ProjectIdFlag
const (
testRegion = "eu02"
)

@@ -40,4 +42,5 @@ type testCtxKey struct{}

flagValues := map[string]string{
projectIdFlag: testProjectId,
instanceIdFlag: testInstanceId,
globalflags.ProjectIdFlag: testProjectId,
globalflags.RegionFlag: testRegion,
instanceIdFlag: testInstanceId,
}

@@ -54,2 +57,3 @@ for _, mod := range mods {

ProjectId: testProjectId,
Region: testRegion,
Verbosity: globalflags.VerbosityDefault,

@@ -67,3 +71,3 @@ },

func fixtureRequest(mods ...func(request *mongodbflex.ApiDeleteUserRequest)) mongodbflex.ApiDeleteUserRequest {
request := testClient.DeleteUser(testCtx, testProjectId, testInstanceId, testUserId)
request := testClient.DeleteUser(testCtx, testProjectId, testInstanceId, testUserId, testRegion)
for _, mod := range mods {

@@ -112,3 +116,3 @@ mod(&request)

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, projectIdFlag)
delete(flagValues, globalflags.ProjectIdFlag)
}),

@@ -121,3 +125,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = ""
flagValues[globalflags.ProjectIdFlag] = ""
}),

@@ -130,3 +134,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = "invalid-uuid"
flagValues[globalflags.ProjectIdFlag] = "invalid-uuid"
}),

@@ -133,0 +137,0 @@ isValid: false,

@@ -62,3 +62,3 @@ package delete

instanceLabel, err := mongodbflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId)
instanceLabel, err := mongodbflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId, model.Region)
if err != nil {

@@ -69,3 +69,3 @@ params.Printer.Debug(print.ErrorLevel, "get instance name: %v", err)

userLabel, err := mongodbflexUtils.GetUserName(ctx, apiClient, model.ProjectId, model.InstanceId, model.UserId)
userLabel, err := mongodbflexUtils.GetUserName(ctx, apiClient, model.ProjectId, model.InstanceId, model.UserId, model.Region)
if err != nil {

@@ -133,4 +133,4 @@ params.Printer.Debug(print.ErrorLevel, "get user name: %v", err)

func buildRequest(ctx context.Context, model *inputModel, apiClient *mongodbflex.APIClient) mongodbflex.ApiDeleteUserRequest {
req := apiClient.DeleteUser(ctx, model.ProjectId, model.InstanceId, model.UserId)
req := apiClient.DeleteUser(ctx, model.ProjectId, model.InstanceId, model.UserId, model.Region)
return req
}

@@ -17,3 +17,5 @@ package describe

var projectIdFlag = globalflags.ProjectIdFlag
const (
testRegion = "eu02"
)

@@ -40,4 +42,5 @@ type testCtxKey struct{}

flagValues := map[string]string{
projectIdFlag: testProjectId,
instanceIdFlag: testInstanceId,
globalflags.ProjectIdFlag: testProjectId,
globalflags.RegionFlag: testRegion,
instanceIdFlag: testInstanceId,
}

@@ -54,2 +57,3 @@ for _, mod := range mods {

ProjectId: testProjectId,
Region: testRegion,
Verbosity: globalflags.VerbosityDefault,

@@ -67,3 +71,3 @@ },

func fixtureRequest(mods ...func(request *mongodbflex.ApiGetUserRequest)) mongodbflex.ApiGetUserRequest {
request := testClient.GetUser(testCtx, testProjectId, testInstanceId, testUserId)
request := testClient.GetUser(testCtx, testProjectId, testInstanceId, testUserId, testRegion)
for _, mod := range mods {

@@ -112,3 +116,3 @@ mod(&request)

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, projectIdFlag)
delete(flagValues, globalflags.ProjectIdFlag)
}),

@@ -121,3 +125,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = ""
flagValues[globalflags.ProjectIdFlag] = ""
}),

@@ -130,3 +134,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = "invalid-uuid"
flagValues[globalflags.ProjectIdFlag] = "invalid-uuid"
}),

@@ -133,0 +137,0 @@ isValid: false,

@@ -117,3 +117,3 @@ package describe

func buildRequest(ctx context.Context, model *inputModel, apiClient *mongodbflex.APIClient) mongodbflex.ApiGetUserRequest {
req := apiClient.GetUser(ctx, model.ProjectId, model.InstanceId, model.UserId)
req := apiClient.GetUser(ctx, model.ProjectId, model.InstanceId, model.UserId, model.Region)
return req

@@ -120,0 +120,0 @@ }

@@ -19,3 +19,5 @@ package list

var projectIdFlag = globalflags.ProjectIdFlag
const (
testRegion = "eu02"
)

@@ -31,5 +33,6 @@ type testCtxKey struct{}

flagValues := map[string]string{
projectIdFlag: testProjectId,
instanceIdFlag: testInstanceId,
limitFlag: "10",
globalflags.ProjectIdFlag: testProjectId,
globalflags.RegionFlag: testRegion,
instanceIdFlag: testInstanceId,
limitFlag: "10",
}

@@ -46,2 +49,3 @@ for _, mod := range mods {

ProjectId: testProjectId,
Region: testRegion,
Verbosity: globalflags.VerbosityDefault,

@@ -59,3 +63,3 @@ },

func fixtureRequest(mods ...func(request *mongodbflex.ApiListUsersRequest)) mongodbflex.ApiListUsersRequest {
request := testClient.ListUsers(testCtx, testProjectId, testInstanceId)
request := testClient.ListUsers(testCtx, testProjectId, testInstanceId, testRegion)
for _, mod := range mods {

@@ -88,3 +92,3 @@ mod(&request)

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, projectIdFlag)
delete(flagValues, globalflags.ProjectIdFlag)
}),

@@ -96,3 +100,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = ""
flagValues[globalflags.ProjectIdFlag] = ""
}),

@@ -104,3 +108,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = "invalid-uuid"
flagValues[globalflags.ProjectIdFlag] = "invalid-uuid"
}),

@@ -107,0 +111,0 @@ isValid: false,

@@ -74,3 +74,3 @@ package list

if resp.Items == nil || len(*resp.Items) == 0 {
instanceLabel, err := mongodbflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, *model.InstanceId)
instanceLabel, err := mongodbflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, *model.InstanceId, model.Region)
if err != nil {

@@ -139,3 +139,3 @@ params.Printer.Debug(print.ErrorLevel, "get instance name: %v", err)

func buildRequest(ctx context.Context, model *inputModel, apiClient *mongodbflex.APIClient) mongodbflex.ApiListUsersRequest {
req := apiClient.ListUsers(ctx, model.ProjectId, *model.InstanceId)
req := apiClient.ListUsers(ctx, model.ProjectId, *model.InstanceId, model.Region)
return req

@@ -142,0 +142,0 @@ }

@@ -17,3 +17,5 @@ package resetpassword

var projectIdFlag = globalflags.ProjectIdFlag
const (
testRegion = "eu02"
)

@@ -40,4 +42,5 @@ type testCtxKey struct{}

flagValues := map[string]string{
projectIdFlag: testProjectId,
instanceIdFlag: testInstanceId,
globalflags.ProjectIdFlag: testProjectId,
globalflags.RegionFlag: testRegion,
instanceIdFlag: testInstanceId,
}

@@ -54,2 +57,3 @@ for _, mod := range mods {

ProjectId: testProjectId,
Region: testRegion,
Verbosity: globalflags.VerbosityDefault,

@@ -67,3 +71,3 @@ },

func fixtureRequest(mods ...func(request *mongodbflex.ApiResetUserRequest)) mongodbflex.ApiResetUserRequest {
request := testClient.ResetUser(testCtx, testProjectId, testInstanceId, testUserId)
request := testClient.ResetUser(testCtx, testProjectId, testInstanceId, testUserId, testRegion)
for _, mod := range mods {

@@ -112,3 +116,3 @@ mod(&request)

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, projectIdFlag)
delete(flagValues, globalflags.ProjectIdFlag)
}),

@@ -121,3 +125,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = ""
flagValues[globalflags.ProjectIdFlag] = ""
}),

@@ -130,3 +134,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = "invalid-uuid"
flagValues[globalflags.ProjectIdFlag] = "invalid-uuid"
}),

@@ -133,0 +137,0 @@ isValid: false,

@@ -64,3 +64,3 @@ package resetpassword

instanceLabel, err := mongodbflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId)
instanceLabel, err := mongodbflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId, model.Region)
if err != nil {

@@ -71,3 +71,3 @@ params.Printer.Debug(print.ErrorLevel, "get instance name: %v", err)

userLabel, err := mongodbflexUtils.GetUserName(ctx, apiClient, model.ProjectId, model.InstanceId, model.UserId)
userLabel, err := mongodbflexUtils.GetUserName(ctx, apiClient, model.ProjectId, model.InstanceId, model.UserId, model.Region)
if err != nil {

@@ -135,3 +135,3 @@ params.Printer.Debug(print.ErrorLevel, "get user name: %v", err)

func buildRequest(ctx context.Context, model *inputModel, apiClient *mongodbflex.APIClient) mongodbflex.ApiResetUserRequest {
req := apiClient.ResetUser(ctx, model.ProjectId, model.InstanceId, model.UserId)
req := apiClient.ResetUser(ctx, model.ProjectId, model.InstanceId, model.UserId, model.Region)
return req

@@ -138,0 +138,0 @@ }

@@ -18,3 +18,5 @@ package update

var projectIdFlag = globalflags.ProjectIdFlag
const (
testRegion = "eu02"
)

@@ -41,5 +43,6 @@ type testCtxKey struct{}

flagValues := map[string]string{
projectIdFlag: testProjectId,
instanceIdFlag: testInstanceId,
databaseFlag: "default",
globalflags.ProjectIdFlag: testProjectId,
globalflags.RegionFlag: testRegion,
instanceIdFlag: testInstanceId,
databaseFlag: "default",
}

@@ -56,2 +59,3 @@ for _, mod := range mods {

ProjectId: testProjectId,
Region: testRegion,
Verbosity: globalflags.VerbosityDefault,

@@ -70,3 +74,3 @@ },

func fixtureRequest(mods ...func(request *mongodbflex.ApiPartialUpdateUserRequest)) mongodbflex.ApiPartialUpdateUserRequest {
request := testClient.PartialUpdateUser(testCtx, testProjectId, testInstanceId, testUserId)
request := testClient.PartialUpdateUser(testCtx, testProjectId, testInstanceId, testUserId, testRegion)
request = request.PartialUpdateUserPayload(mongodbflex.PartialUpdateUserPayload{

@@ -135,3 +139,3 @@ Database: utils.Ptr("default"),

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, projectIdFlag)
delete(flagValues, globalflags.ProjectIdFlag)
}),

@@ -144,3 +148,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = ""
flagValues[globalflags.ProjectIdFlag] = ""
}),

@@ -153,3 +157,3 @@ isValid: false,

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = "invalid-uuid"
flagValues[globalflags.ProjectIdFlag] = "invalid-uuid"
}),

@@ -156,0 +160,0 @@ isValid: false,

@@ -63,3 +63,3 @@ package update

instanceLabel, err := mongodbflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId)
instanceLabel, err := mongodbflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId, model.Region)
if err != nil {

@@ -70,3 +70,3 @@ params.Printer.Debug(print.ErrorLevel, "get instance name: %v", err)

userLabel, err := mongodbflexUtils.GetUserName(ctx, apiClient, model.ProjectId, model.InstanceId, model.UserId)
userLabel, err := mongodbflexUtils.GetUserName(ctx, apiClient, model.ProjectId, model.InstanceId, model.UserId, model.Region)
if err != nil {

@@ -102,3 +102,3 @@ params.Printer.Debug(print.ErrorLevel, "get user name: %v", err)

func configureFlags(cmd *cobra.Command) {
roleOptions := []string{"read", "readWrite"}
roleOptions := []string{"read", "readWrite", "readWriteAnyDatabase"}

@@ -149,3 +149,3 @@ cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "ID of the instance")

func buildRequest(ctx context.Context, model *inputModel, apiClient *mongodbflex.APIClient) mongodbflex.ApiPartialUpdateUserRequest {
req := apiClient.PartialUpdateUser(ctx, model.ProjectId, model.InstanceId, model.UserId)
req := apiClient.PartialUpdateUser(ctx, model.ProjectId, model.InstanceId, model.UserId, model.Region)
req = req.PartialUpdateUserPayload(mongodbflex.PartialUpdateUserPayload{

@@ -152,0 +152,0 @@ Database: model.Database,

@@ -159,5 +159,5 @@ package create

default:
p.Outputf("Created security group %q\n", name)
p.Outputf("Created security group %q.\nSecurity Group ID %s\n", name, utils.PtrString(resp.Id))
return nil
}
}

@@ -9,4 +9,8 @@ package cluster

generatepayload "github.com/stackitcloud/stackit-cli/internal/cmd/ske/cluster/generate-payload"
"github.com/stackitcloud/stackit-cli/internal/cmd/ske/cluster/hibernate"
"github.com/stackitcloud/stackit-cli/internal/cmd/ske/cluster/list"
"github.com/stackitcloud/stackit-cli/internal/cmd/ske/cluster/maintenance"
"github.com/stackitcloud/stackit-cli/internal/cmd/ske/cluster/reconcile"
"github.com/stackitcloud/stackit-cli/internal/cmd/ske/cluster/update"
"github.com/stackitcloud/stackit-cli/internal/cmd/ske/cluster/wakeup"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"

@@ -37,2 +41,6 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/utils"

cmd.AddCommand(update.NewCmd(params))
cmd.AddCommand(hibernate.NewCmd(params))
cmd.AddCommand(maintenance.NewCmd(params))
cmd.AddCommand(reconcile.NewCmd(params))
cmd.AddCommand(wakeup.NewCmd(params))
}

@@ -43,3 +43,3 @@ package create

Use: fmt.Sprintf("create %s", clusterNameArg),
Short: "Creates an SKE cluster",
Short: "Creates a SKE cluster",
Long: fmt.Sprintf("%s\n%s\n%s",

@@ -53,9 +53,9 @@ "Creates a STACKIT Kubernetes Engine (SKE) cluster.",

examples.NewExample(
`Create an SKE cluster using default configuration`,
`Create a SKE cluster using default configuration`,
"$ stackit ske cluster create my-cluster"),
examples.NewExample(
`Create an SKE cluster using an API payload sourced from the file "./payload.json"`,
`Create a SKE cluster using an API payload sourced from the file "./payload.json"`,
"$ stackit ske cluster create my-cluster --payload @./payload.json"),
examples.NewExample(
`Create an SKE cluster using an API payload provided as a JSON string`,
`Create a SKE cluster using an API payload provided as a JSON string`,
`$ stackit ske cluster create my-cluster --payload "{...}"`),

@@ -62,0 +62,0 @@ examples.NewExample(

@@ -38,3 +38,3 @@ package delete

examples.NewExample(
`Delete an SKE cluster with name "my-cluster"`,
`Delete a SKE cluster with name "my-cluster"`,
"$ stackit ske cluster delete my-cluster"),

@@ -41,0 +41,0 @@ ),

@@ -34,11 +34,11 @@ package describe

Use: fmt.Sprintf("describe %s", clusterNameArg),
Short: "Shows details of a SKE cluster",
Long: "Shows details of a STACKIT Kubernetes Engine (SKE) cluster.",
Short: "Shows details of a SKE cluster",
Long: "Shows details of a STACKIT Kubernetes Engine (SKE) cluster.",
Args: args.SingleArg(clusterNameArg, nil),
Example: examples.Build(
examples.NewExample(
`Get details of an SKE cluster with name "my-cluster"`,
`Get details of a SKE cluster with name "my-cluster"`,
"$ stackit ske cluster describe my-cluster"),
examples.NewExample(
`Get details of an SKE cluster with name "my-cluster" in JSON format`,
`Get details of a SKE cluster with name "my-cluster" in JSON format`,
"$ stackit ske cluster describe my-cluster --output-format json"),

@@ -45,0 +45,0 @@ ),

@@ -40,3 +40,3 @@ package update

Use: fmt.Sprintf("update %s", clusterNameArg),
Short: "Updates an SKE cluster",
Short: "Updates a SKE cluster",
Long: fmt.Sprintf("%s\n%s\n%s",

@@ -50,6 +50,6 @@ "Updates a STACKIT Kubernetes Engine (SKE) cluster.",

examples.NewExample(
`Update an SKE cluster using an API payload sourced from the file "./payload.json"`,
`Update a SKE cluster using an API payload sourced from the file "./payload.json"`,
"$ stackit ske cluster update my-cluster --payload @./payload.json"),
examples.NewExample(
`Update an SKE cluster using an API payload provided as a JSON string`,
`Update a SKE cluster using an API payload provided as a JSON string`,
`$ stackit ske cluster update my-cluster --payload "{...}"`),

@@ -56,0 +56,0 @@ examples.NewExample(

@@ -47,5 +47,5 @@ package create

Use: fmt.Sprintf("create %s", clusterNameArg),
Short: "Creates or update a kubeconfig for an SKE cluster",
Short: "Creates or update a kubeconfig for a SKE cluster",
Long: fmt.Sprintf("%s\n\n%s\n%s\n%s\n%s",
"Creates a kubeconfig for a STACKIT Kubernetes Engine (SKE) cluster, if the config exits in the kubeconfig file the information will be updated.",
"Creates a kubeconfig for a STACKIT Kubernetes Engine (SKE) cluster, if the config exists in the kubeconfig file the information will be updated.",
"By default, the kubeconfig information of the SKE cluster is merged into the default kubeconfig file of the current user. If the kubeconfig file doesn't exist, a new one will be created.",

@@ -52,0 +52,0 @@ "You can override this behavior by specifying a custom filepath with the --filepath flag.\n",

@@ -31,9 +31,9 @@ package ske

func addSubcommands(cmd *cobra.Command, params *params.CmdParams) {
cmd.AddCommand(cluster.NewCmd(params))
cmd.AddCommand(credentials.NewCmd(params))
cmd.AddCommand(describe.NewCmd(params))
cmd.AddCommand(disable.NewCmd(params))
cmd.AddCommand(enable.NewCmd(params))
cmd.AddCommand(kubeconfig.NewCmd(params))
cmd.AddCommand(disable.NewCmd(params))
cmd.AddCommand(cluster.NewCmd(params))
cmd.AddCommand(credentials.NewCmd(params))
cmd.AddCommand(options.NewCmd(params))
}

@@ -5,2 +5,3 @@ package auth

"fmt"
"net/http"
"os"

@@ -136,1 +137,69 @@ "strconv"

}
// RefreshAccessToken refreshes the access token if it's expired for the user token flow.
// It returns the new access token or an error if the refresh fails.
func RefreshAccessToken(p *print.Printer) (string, error) {
flow, err := GetAuthFlow()
if err != nil {
return "", fmt.Errorf("get authentication flow: %w", err)
}
if flow != AUTH_FLOW_USER_TOKEN {
return "", fmt.Errorf("token refresh is only supported for user token flow, current flow: %s", flow)
}
// Load tokens from storage
authFields := map[authFieldKey]string{
ACCESS_TOKEN: "",
REFRESH_TOKEN: "",
IDP_TOKEN_ENDPOINT: "",
}
err = GetAuthFieldMap(authFields)
if err != nil {
return "", fmt.Errorf("get tokens from auth storage: %w", err)
}
accessToken := authFields[ACCESS_TOKEN]
refreshToken := authFields[REFRESH_TOKEN]
tokenEndpoint := authFields[IDP_TOKEN_ENDPOINT]
if accessToken == "" {
return "", fmt.Errorf("access token not set")
}
if refreshToken == "" {
return "", fmt.Errorf("refresh token not set")
}
if tokenEndpoint == "" {
return "", fmt.Errorf("token endpoint not set")
}
// Check if access token is expired
accessTokenExpired, err := TokenExpired(accessToken)
if err != nil {
return "", fmt.Errorf("check if access token has expired: %w", err)
}
if !accessTokenExpired {
// Token is still valid, return it
return accessToken, nil
}
p.Debug(print.DebugLevel, "access token expired, refreshing...")
// Create a temporary userTokenFlow to reuse the refresh logic
utf := &userTokenFlow{
printer: p,
client: &http.Client{},
authFlow: flow,
accessToken: accessToken,
refreshToken: refreshToken,
tokenEndpoint: tokenEndpoint,
}
// Refresh the tokens
err = refreshTokens(utf)
if err != nil {
return "", fmt.Errorf("refresh access token: %w", err)
}
// Return the new access token
return utf.accessToken, nil
}

@@ -21,6 +21,4 @@ package client

}
region := viper.GetString(config.RegionKey)
cfgOptions := []sdkConfig.ConfigurationOption{
utils.UserAgentConfigOption(cliVersion),
sdkConfig.WithRegion(region),
authCfgOption,

@@ -30,3 +28,2 @@ }

customEndpoint := viper.GetString(config.MongoDBFlexCustomEndpointKey)
if customEndpoint != "" {

@@ -33,0 +30,0 @@ cfgOptions = append(cfgOptions, sdkConfig.WithEndpoint(customEndpoint))

@@ -23,2 +23,3 @@ package utils

const (
testRegion = "eu02"
testInstanceName = "instance"

@@ -32,3 +33,3 @@ testUserName = "user"

getInstanceFails bool
getInstanceResp *mongodbflex.GetInstanceResponse
getInstanceResp *mongodbflex.InstanceResponse
getUserFails bool

@@ -40,3 +41,3 @@ getUserResp *mongodbflex.GetUserResponse

func (m *mongoDBFlexClientMocked) ListVersionsExecute(_ context.Context, _ string) (*mongodbflex.ListVersionsResponse, error) {
func (m *mongoDBFlexClientMocked) ListVersionsExecute(_ context.Context, _, _ string) (*mongodbflex.ListVersionsResponse, error) {
if m.listVersionsFails {

@@ -48,3 +49,3 @@ return nil, fmt.Errorf("could not list versions")

func (m *mongoDBFlexClientMocked) ListRestoreJobsExecute(_ context.Context, _, _ string) (*mongodbflex.ListRestoreJobsResponse, error) {
func (m *mongoDBFlexClientMocked) ListRestoreJobsExecute(_ context.Context, _, _, _ string) (*mongodbflex.ListRestoreJobsResponse, error) {
if m.listRestoreJobsFails {

@@ -56,3 +57,3 @@ return nil, fmt.Errorf("could not list versions")

func (m *mongoDBFlexClientMocked) GetInstanceExecute(_ context.Context, _, _ string) (*mongodbflex.GetInstanceResponse, error) {
func (m *mongoDBFlexClientMocked) GetInstanceExecute(_ context.Context, _, _, _ string) (*mongodbflex.InstanceResponse, error) {
if m.getInstanceFails {

@@ -64,3 +65,3 @@ return nil, fmt.Errorf("could not get instance")

func (m *mongoDBFlexClientMocked) GetUserExecute(_ context.Context, _, _, _ string) (*mongodbflex.GetUserResponse, error) {
func (m *mongoDBFlexClientMocked) GetUserExecute(_ context.Context, _, _, _, _ string) (*mongodbflex.GetUserResponse, error) {
if m.getUserFails {

@@ -184,3 +185,3 @@ return nil, fmt.Errorf("could not get user")

flavorId string
flavors *[]mongodbflex.HandlersInfraFlavor
flavors *[]mongodbflex.InstanceFlavor
isValid bool

@@ -191,3 +192,3 @@ }{

flavorId: "foo",
flavors: &[]mongodbflex.HandlersInfraFlavor{
flavors: &[]mongodbflex.InstanceFlavor{
{Id: utils.Ptr("bar-1")},

@@ -208,3 +209,3 @@ {Id: utils.Ptr("bar-2")},

flavorId: "foo",
flavors: &[]mongodbflex.HandlersInfraFlavor{},
flavors: &[]mongodbflex.InstanceFlavor{},
isValid: false,

@@ -215,3 +216,3 @@ },

flavorId: "foo",
flavors: &[]mongodbflex.HandlersInfraFlavor{
flavors: &[]mongodbflex.InstanceFlavor{
{Id: utils.Ptr("bar-1")},

@@ -226,3 +227,3 @@ {Id: nil},

flavorId: "foo",
flavors: &[]mongodbflex.HandlersInfraFlavor{
flavors: &[]mongodbflex.InstanceFlavor{
{Id: utils.Ptr("bar-1")},

@@ -254,3 +255,3 @@ {Id: utils.Ptr("bar-2")},

ram int64
flavors *[]mongodbflex.HandlersInfraFlavor
flavors *[]mongodbflex.InstanceFlavor
isValid bool

@@ -263,3 +264,3 @@ expectedOutput *string

ram: 4,
flavors: &[]mongodbflex.HandlersInfraFlavor{
flavors: &[]mongodbflex.InstanceFlavor{
{

@@ -295,3 +296,3 @@ Id: utils.Ptr("bar-1"),

ram: 4,
flavors: &[]mongodbflex.HandlersInfraFlavor{},
flavors: &[]mongodbflex.InstanceFlavor{},
isValid: false,

@@ -303,3 +304,3 @@ },

ram: 4,
flavors: &[]mongodbflex.HandlersInfraFlavor{
flavors: &[]mongodbflex.InstanceFlavor{
{

@@ -328,3 +329,3 @@ Id: utils.Ptr("bar-1"),

ram: 4,
flavors: &[]mongodbflex.HandlersInfraFlavor{
flavors: &[]mongodbflex.InstanceFlavor{
{

@@ -352,3 +353,3 @@ Id: utils.Ptr("bar-1"),

ram: 4,
flavors: &[]mongodbflex.HandlersInfraFlavor{
flavors: &[]mongodbflex.InstanceFlavor{
{

@@ -431,3 +432,3 @@ Id: utils.Ptr("bar-1"),

output, err := GetLatestMongoDBVersion(context.Background(), client, testProjectId)
output, err := GetLatestMongoDBVersion(context.Background(), client, testProjectId, testRegion)

@@ -454,3 +455,3 @@ if tt.isValid && err != nil {

getInstanceFails bool
getInstanceResp *mongodbflex.GetInstanceResponse
getInstanceResp *mongodbflex.InstanceResponse
isValid bool

@@ -461,3 +462,3 @@ expectedOutput string

description: "base",
getInstanceResp: &mongodbflex.GetInstanceResponse{
getInstanceResp: &mongodbflex.InstanceResponse{
Item: &mongodbflex.Instance{

@@ -484,3 +485,3 @@ Name: utils.Ptr(testInstanceName),

output, err := GetInstanceName(context.Background(), client, testProjectId, testInstanceId)
output, err := GetInstanceName(context.Background(), client, testProjectId, testInstanceId, testRegion)

@@ -535,3 +536,3 @@ if tt.isValid && err != nil {

output, err := GetUserName(context.Background(), client, testProjectId, testInstanceId, testUserId)
output, err := GetUserName(context.Background(), client, testProjectId, testInstanceId, testUserId, testRegion)

@@ -538,0 +539,0 @@ if tt.isValid && err != nil {

@@ -24,6 +24,6 @@ package utils

type MongoDBFlexClient interface {
ListVersionsExecute(ctx context.Context, projectId string) (*mongodbflex.ListVersionsResponse, error)
GetInstanceExecute(ctx context.Context, projectId, instanceId string) (*mongodbflex.GetInstanceResponse, error)
GetUserExecute(ctx context.Context, projectId, instanceId, userId string) (*mongodbflex.GetUserResponse, error)
ListRestoreJobsExecute(ctx context.Context, projectId string, instanceId string) (*mongodbflex.ListRestoreJobsResponse, error)
ListVersionsExecute(ctx context.Context, projectId, region string) (*mongodbflex.ListVersionsResponse, error)
GetInstanceExecute(ctx context.Context, projectId, instanceId, region string) (*mongodbflex.InstanceResponse, error)
GetUserExecute(ctx context.Context, projectId, instanceId, userId, region string) (*mongodbflex.GetUserResponse, error)
ListRestoreJobsExecute(ctx context.Context, projectId string, instanceId, region string) (*mongodbflex.ListRestoreJobsResponse, error)
}

@@ -61,3 +61,3 @@

func ValidateFlavorId(flavorId string, flavors *[]mongodbflex.HandlersInfraFlavor) error {
func ValidateFlavorId(flavorId string, flavors *[]mongodbflex.InstanceFlavor) error {
if flavors == nil {

@@ -106,3 +106,3 @@ return fmt.Errorf("nil flavors")

func LoadFlavorId(cpu, ram int64, flavors *[]mongodbflex.HandlersInfraFlavor) (*string, error) {
func LoadFlavorId(cpu, ram int64, flavors *[]mongodbflex.InstanceFlavor) (*string, error) {
if flavors == nil {

@@ -128,4 +128,4 @@ return nil, fmt.Errorf("nil flavors")

func GetLatestMongoDBVersion(ctx context.Context, apiClient MongoDBFlexClient, projectId string) (string, error) {
resp, err := apiClient.ListVersionsExecute(ctx, projectId)
func GetLatestMongoDBVersion(ctx context.Context, apiClient MongoDBFlexClient, projectId, region string) (string, error) {
resp, err := apiClient.ListVersionsExecute(ctx, projectId, region)
if err != nil {

@@ -151,4 +151,4 @@ return "", fmt.Errorf("get MongoDB versions: %w", err)

func GetInstanceName(ctx context.Context, apiClient MongoDBFlexClient, projectId, instanceId string) (string, error) {
resp, err := apiClient.GetInstanceExecute(ctx, projectId, instanceId)
func GetInstanceName(ctx context.Context, apiClient MongoDBFlexClient, projectId, instanceId, region string) (string, error) {
resp, err := apiClient.GetInstanceExecute(ctx, projectId, instanceId, region)
if err != nil {

@@ -160,4 +160,4 @@ return "", fmt.Errorf("get MongoDB Flex instance: %w", err)

func GetUserName(ctx context.Context, apiClient MongoDBFlexClient, projectId, instanceId, userId string) (string, error) {
resp, err := apiClient.GetUserExecute(ctx, projectId, instanceId, userId)
func GetUserName(ctx context.Context, apiClient MongoDBFlexClient, projectId, instanceId, userId, region string) (string, error) {
resp, err := apiClient.GetUserExecute(ctx, projectId, instanceId, userId, region)
if err != nil {

@@ -164,0 +164,0 @@ return "", fmt.Errorf("get MongoDB Flex user: %w", err)

## stackit git create
Creates STACKIT Git instance
### Synopsis
Create a STACKIT Git instance by name.
```
stackit git create [flags]
```
### Examples
```
Create a instance with name 'my-new-instance'
$ stackit git create --name my-new-instance
```
### Options
```
-h, --help Help for "stackit git create"
--name string The name of the instance.
```
### 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 git](./stackit_git.md) - Provides functionality for STACKIT Git
## stackit git delete
Deletes STACKIT Git instance
### Synopsis
Deletes a STACKIT Git instance by its internal ID.
```
stackit git delete INSTANCE_ID [flags]
```
### Examples
```
Delete a instance with ID "xxx"
$ stackit git delete xxx
```
### Options
```
-h, --help Help for "stackit git delete"
```
### 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 git](./stackit_git.md) - Provides functionality for STACKIT Git
## stackit git describe
Describes STACKIT Git instance
### Synopsis
Describes a STACKIT Git instance by its internal ID.
```
stackit git describe INSTANCE_ID [flags]
```
### Examples
```
Describe instance "xxx"
$ stackit git describe xxx
```
### Options
```
-h, --help Help for "stackit git describe"
```
### 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 git](./stackit_git.md) - Provides functionality for STACKIT Git
## stackit git list
Lists all instances of STACKIT Git.
### Synopsis
Lists all instances of STACKIT Git for the current project.
```
stackit git list [flags]
```
### Examples
```
List all STACKIT Git instances
$ stackit git instance list
Lists up to 10 STACKIT Git instances
$ stackit git instance list --limit=10
```
### Options
```
-h, --help Help for "stackit git list"
--limit int Limit the output to the first n elements
```
### 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 git](./stackit_git.md) - Provides functionality for STACKIT Git
package create
import (
"context"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/stackitcloud/stackit-cli/internal/cmd/params"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/git"
)
type testCtxKey struct{}
var (
testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
testClient = &git.APIClient{}
testProjectId = uuid.NewString()
testName = "test-instance"
)
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
globalflags.ProjectIdFlag: testProjectId,
nameFlag: testName,
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{ProjectId: testProjectId, Verbosity: globalflags.VerbosityDefault},
Name: testName,
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureCreatePayload(mods ...func(payload *git.CreateInstancePayload)) (payload git.CreateInstancePayload) {
payload = git.CreateInstancePayload{
Name: &testName,
}
for _, mod := range mods {
mod(&payload)
}
return payload
}
func fixtureRequest(mods ...func(request *git.ApiCreateInstanceRequest)) git.ApiCreateInstanceRequest {
request := testClient.CreateInstance(testCtx, testProjectId)
request = request.CreateInstancePayload(fixtureCreatePayload())
for _, mod := range mods {
mod(&request)
}
return request
}
func TestParseInput(t *testing.T) {
tests := []struct {
description string
flagValues map[string]string
isValid bool
expectedModel *inputModel
}{
{
description: "base",
flagValues: fixtureFlagValues(),
isValid: true,
expectedModel: fixtureInputModel(),
},
{
description: "no values",
flagValues: map[string]string{},
isValid: false,
},
{
description: "project id missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, globalflags.ProjectIdFlag)
}),
isValid: false,
},
{
description: "project id invalid 1",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[globalflags.ProjectIdFlag] = ""
}),
isValid: false,
},
{
description: "project id invalid 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[globalflags.ProjectIdFlag] = "invalid-uuid"
}),
isValid: false,
},
{
description: "name missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, nameFlag)
}),
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
p := print.NewPrinter()
cmd := NewCmd(&params.CmdParams{Printer: p})
if err := globalflags.Configure(cmd.Flags()); err != nil {
t.Errorf("cannot configure global flags: %v", err)
}
for flag, value := range tt.flagValues {
err := cmd.Flags().Set(flag, value)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("setting flag --%s=%s: %v", flag, value, err)
}
}
if err := cmd.ValidateFlagGroups(); err != nil {
if !tt.isValid {
return
}
t.Fatalf("error validating flag groups: %v", err)
}
if err := cmd.ValidateRequiredFlags(); err != nil {
if !tt.isValid {
return
}
t.Fatalf("error validating flags: %v", err)
}
model, err := parseInput(p, cmd)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error parsing flags: %v", err)
}
if !tt.isValid {
t.Fatalf("did not fail on invalid input")
}
diff := cmp.Diff(model, tt.expectedModel)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest git.ApiCreateInstanceRequest
}{
{
description: "base",
model: fixtureInputModel(),
expectedRequest: fixtureRequest(),
},
{
description: "name flag",
model: fixtureInputModel(func(model *inputModel) {
model.Name = "new-name"
}),
expectedRequest: fixtureRequest(func(request *git.ApiCreateInstanceRequest) {
*request = (*request).CreateInstancePayload(fixtureCreatePayload(func(payload *git.CreateInstancePayload) {
payload.Name = utils.Ptr("new-name")
}))
}),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
request := buildRequest(testCtx, tt.model, testClient)
diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmpopts.EquateComparable(testCtx),
cmp.AllowUnexported(git.NullableString{}),
)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
func TestOutputResult(t *testing.T) {
type args struct {
model *inputModel
resp *git.Instance
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "nil",
args: args{
model: nil,
resp: nil,
},
wantErr: true,
},
{
name: "empty input",
args: args{
model: &inputModel{},
resp: &git.Instance{},
},
wantErr: false,
},
{
name: "output json",
args: args{
model: &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
OutputFormat: print.JSONOutputFormat,
},
},
resp: nil,
},
wantErr: false,
},
}
p := print.NewPrinter()
p.Cmd = NewCmd(&params.CmdParams{Printer: p})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := outputResult(p, tt.args.model, tt.args.resp); (err != nil) != tt.wantErr {
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
package create
import (
"context"
"encoding/json"
"fmt"
"github.com/stackitcloud/stackit-cli/internal/cmd/params"
"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/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/git/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/spinner"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/git"
"github.com/stackitcloud/stackit-sdk-go/services/git/wait"
)
const (
nameFlag = "name"
)
type inputModel struct {
*globalflags.GlobalFlagModel
Id *string
Name string
}
func NewCmd(params *params.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: "create",
Short: "Creates STACKIT Git instance",
Long: "Create a STACKIT Git instance by name.",
Args: args.NoArgs,
Example: examples.Build(
examples.NewExample(
`Create a instance with name 'my-new-instance'`,
`$ stackit git create --name my-new-instance`,
),
),
RunE: func(cmd *cobra.Command, _ []string) (err error) {
ctx := context.Background()
model, err := parseInput(params.Printer, cmd)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(params.Printer)
if err != nil {
return err
}
if !model.AssumeYes {
prompt := fmt.Sprintf("Are you sure you want to create the instance %q?", model.Name)
err = params.Printer.PromptForConfirmation(prompt)
if err != nil {
return err
}
}
// Call API
request := buildRequest(ctx, model, apiClient)
result, err := request.Execute()
if err != nil {
return fmt.Errorf("create stackit git instance: %w", err)
}
model.Id = result.Id
// Wait for async operation, if async mode not enabled
if !model.Async {
s := spinner.New(params.Printer)
s.Start("Creating stackit git instance")
_, err = wait.CreateGitInstanceWaitHandler(ctx, apiClient, model.ProjectId, *model.Id).WaitWithContext(ctx)
if err != nil {
return fmt.Errorf("wait for stackit git Instance creation: %w", err)
}
s.Stop()
}
return outputResult(params.Printer, model, result)
},
}
configureFlags(cmd)
return cmd
}
func configureFlags(cmd *cobra.Command) {
cmd.Flags().String(nameFlag, "", "The name of the instance.")
if err := flags.MarkFlagsRequired(cmd, nameFlag); err != nil {
cobra.CheckErr(err)
}
}
func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)
if globalFlags.ProjectId == "" {
return nil, &errors.ProjectIdError{}
}
name := flags.FlagToStringValue(p, cmd, nameFlag)
model := inputModel{
GlobalFlagModel: globalFlags,
Name: name,
}
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 *git.APIClient) git.ApiCreateInstanceRequest {
return apiClient.CreateInstance(ctx, model.ProjectId).CreateInstancePayload(createPayload(model))
}
func createPayload(model *inputModel) git.CreateInstancePayload {
return git.CreateInstancePayload{
Name: &model.Name,
}
}
func outputResult(p *print.Printer, model *inputModel, resp *git.Instance) error {
if model == nil {
return fmt.Errorf("input model is nil")
}
var outputFormat string
if model.GlobalFlagModel != nil {
outputFormat = model.OutputFormat
}
switch outputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(resp, "", " ")
if err != nil {
return fmt.Errorf("marshal instance: %w", err)
}
p.Outputln(string(details))
return nil
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true), yaml.UseJSONMarshaler())
if err != nil {
return fmt.Errorf("marshal iminstanceage: %w", err)
}
p.Outputln(string(details))
return nil
default:
p.Outputf("Created instance %q with id %s\n", model.Name, utils.PtrString(model.Id))
return nil
}
}
package delete
import (
"context"
"testing"
"github.com/stackitcloud/stackit-cli/internal/cmd/params"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/stackitcloud/stackit-sdk-go/services/git"
)
type testCtxKey struct{}
var (
testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
testClient = &git.APIClient{}
testProjectId = uuid.NewString()
testInstanceId = uuid.NewString()
)
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
globalflags.ProjectIdFlag: testProjectId,
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{ProjectId: testProjectId, Verbosity: globalflags.VerbosityDefault},
InstanceId: testInstanceId,
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureRequest(mods ...func(request *git.ApiDeleteInstanceRequest)) git.ApiDeleteInstanceRequest {
request := testClient.DeleteInstance(testCtx, testProjectId, testInstanceId)
for _, mod := range mods {
mod(&request)
}
return request
}
func TestParseInput(t *testing.T) {
tests := []struct {
description string
flagValues map[string]string
args []string
isValid bool
expectedModel *inputModel
}{
{
description: "base",
flagValues: fixtureFlagValues(),
args: []string{testInstanceId},
isValid: true,
expectedModel: fixtureInputModel(),
},
{
description: "project id invalid 1",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[globalflags.ProjectIdFlag] = ""
}),
isValid: false,
},
{
description: "project id invalid 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[globalflags.ProjectIdFlag] = "invalid-uuid"
}),
isValid: false,
},
{
description: "no arguments",
flagValues: fixtureFlagValues(),
args: nil,
isValid: false,
},
{
description: "multiple arguments",
flagValues: fixtureFlagValues(),
args: []string{"foo", "bar"},
isValid: false,
},
{
description: "invalid instance id",
flagValues: fixtureFlagValues(),
args: []string{"foo"},
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
p := print.NewPrinter()
cmd := NewCmd(&params.CmdParams{Printer: p})
err := globalflags.Configure(cmd.Flags())
if err != nil {
t.Fatalf("configure global flags: %v", err)
}
cmd.SetArgs(tt.args)
for flag, value := range tt.flagValues {
err := cmd.Flags().Set(flag, value)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("setting flag --%s=%s: %v", flag, value, err)
}
}
if err := cmd.ValidateArgs(tt.args); err != nil {
if !tt.isValid {
return
}
}
err = cmd.ValidateRequiredFlags()
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error validating flags: %v", err)
}
model, err := parseInput(p, cmd, tt.args)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error parsing flags: %v", err)
}
if !tt.isValid {
t.Fatalf("did not fail on invalid input")
}
diff := cmp.Diff(model, tt.expectedModel)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest git.ApiDeleteInstanceRequest
}{
{
description: "base",
model: fixtureInputModel(),
expectedRequest: fixtureRequest(),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
request := buildRequest(testCtx, tt.model, testClient)
diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmpopts.EquateComparable(testCtx),
)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
package delete
import (
"context"
"fmt"
"github.com/stackitcloud/stackit-cli/internal/cmd/params"
"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/git/client"
gitUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/git/utils"
"github.com/stackitcloud/stackit-cli/internal/pkg/spinner"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/git"
"github.com/stackitcloud/stackit-sdk-go/services/git/wait"
)
type inputModel struct {
*globalflags.GlobalFlagModel
InstanceId string
}
const instanceIdArg = "INSTANCE_ID"
func NewCmd(params *params.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: fmt.Sprintf("delete %s", instanceIdArg),
Short: "Deletes STACKIT Git instance",
Long: "Deletes a STACKIT Git instance by its internal ID.",
Args: args.SingleArg(instanceIdArg, utils.ValidateUUID),
Example: examples.Build(
examples.NewExample(`Delete a instance with ID "xxx"`, `$ stackit git delete xxx`),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(params.Printer, cmd, args)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(params.Printer)
if err != nil {
return err
}
projectName, err := projectname.GetProjectName(ctx, params.Printer, params.CliVersion, cmd)
if err != nil {
params.Printer.Debug(print.ErrorLevel, "get project name: %v", err)
projectName = model.ProjectId
}
instanceName, err := gitUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId)
if err != nil {
params.Printer.Debug(print.ErrorLevel, "get stackit git intance name: %v", err)
instanceName = model.InstanceId
} else if instanceName == "" {
instanceName = model.InstanceId
}
if !model.AssumeYes {
prompt := fmt.Sprintf("Are you sure you want to delete the stackit git instance %q for %q?", instanceName, projectName)
err = params.Printer.PromptForConfirmation(prompt)
if err != nil {
return err
}
}
// Call API
request := buildRequest(ctx, model, apiClient)
err = request.Execute()
if err != nil {
return fmt.Errorf("delete instance: %w", err)
}
// Wait for async operation, if async mode not enabled
if !model.Async {
s := spinner.New(params.Printer)
s.Start("Deleting stackit git instance")
_, err = wait.DeleteGitInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId).WaitWithContext(ctx)
if err != nil {
return fmt.Errorf("wait for stackit git instance deletion: %w", err)
}
s.Stop()
}
operationState := "Deleted"
if model.Async {
operationState = "Triggered deletion of"
}
params.Printer.Info("%s stackit git instance %s \n", operationState, model.InstanceId)
return nil
},
}
return cmd
}
func parseInput(p *print.Printer, cmd *cobra.Command, cliArgs []string) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)
if globalFlags.ProjectId == "" {
return nil, &errors.ProjectIdError{}
}
model := inputModel{
GlobalFlagModel: globalFlags,
InstanceId: cliArgs[0],
}
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 *git.APIClient) git.ApiDeleteInstanceRequest {
return apiClient.DeleteInstance(ctx, model.ProjectId, model.InstanceId)
}
package describe
import (
"context"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/stackitcloud/stackit-cli/internal/cmd/params"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-sdk-go/services/git"
)
type testCtxKey struct{}
var (
testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
testClient = &git.APIClient{}
testProjectId = uuid.NewString()
testInstanceId = []string{uuid.NewString()}
)
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
globalflags.ProjectIdFlag: testProjectId,
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{ProjectId: testProjectId, Verbosity: globalflags.VerbosityDefault},
InstanceId: testInstanceId[0],
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureRequest(mods ...func(request *git.ApiGetInstanceRequest)) git.ApiGetInstanceRequest {
request := testClient.GetInstance(testCtx, testProjectId, testInstanceId[0])
for _, mod := range mods {
mod(&request)
}
return request
}
func TestParseInput(t *testing.T) {
tests := []struct {
description string
flagValues map[string]string
isValid bool
args []string
expectedModel *inputModel
}{
{
description: "base",
flagValues: fixtureFlagValues(),
expectedModel: fixtureInputModel(),
args: testInstanceId,
isValid: true,
},
{
description: "no values",
flagValues: map[string]string{},
args: testInstanceId,
isValid: false,
},
{
description: "project id missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, globalflags.ProjectIdFlag)
}),
args: testInstanceId,
isValid: false,
},
{
description: "project id invalid 1",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[globalflags.ProjectIdFlag] = ""
}),
args: testInstanceId,
isValid: false,
},
{
description: "project id invalid 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[globalflags.ProjectIdFlag] = "invalid-uuid"
}),
args: testInstanceId,
isValid: false,
},
{
description: "no instance id passed",
flagValues: fixtureFlagValues(),
args: nil,
isValid: false,
},
{
description: "multiple instance ids passed",
flagValues: fixtureFlagValues(),
args: []string{uuid.NewString(), uuid.NewString()},
isValid: false,
},
{
description: "invalid instance id passed",
flagValues: fixtureFlagValues(),
args: []string{"foobar"},
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
p := print.NewPrinter()
cmd := NewCmd(&params.CmdParams{Printer: p})
if err := globalflags.Configure(cmd.Flags()); err != nil {
t.Errorf("cannot configure global flags: %v", err)
}
for flag, value := range tt.flagValues {
err := cmd.Flags().Set(flag, value)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("setting flag --%s=%s: %v", flag, value, err)
}
}
if err := cmd.ValidateRequiredFlags(); err != nil {
if !tt.isValid {
return
}
t.Fatalf("error validating flags: %v", err)
}
if err := cmd.ValidateArgs(tt.args); err != nil {
if !tt.isValid {
return
}
}
model, err := parseInput(p, cmd, tt.args)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error parsing flags: %v", err)
}
if !tt.isValid {
t.Fatalf("did not fail on invalid input")
}
diff := cmp.Diff(model, tt.expectedModel)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest git.ApiGetInstanceRequest
}{
{
description: "base",
model: fixtureInputModel(),
expectedRequest: fixtureRequest(),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
request := buildRequest(testCtx, tt.model, testClient)
diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmpopts.EquateComparable(testCtx),
)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
func TestOutputResult(t *testing.T) {
type args struct {
outputFormat string
resp *git.Instance
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "empty",
args: args{
resp: &git.Instance{},
},
wantErr: false,
},
{
name: "nil",
args: args{},
wantErr: true,
},
}
p := print.NewPrinter()
p.Cmd = NewCmd(&params.CmdParams{Printer: p})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := outputResult(p, tt.args.outputFormat, tt.args.resp); (err != nil) != tt.wantErr {
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
package describe
import (
"context"
"encoding/json"
"fmt"
"github.com/stackitcloud/stackit-cli/internal/cmd/params"
"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/services/git/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/git"
)
type inputModel struct {
*globalflags.GlobalFlagModel
InstanceId string
}
const instanceIdArg = "INSTANCE_ID"
func NewCmd(params *params.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: fmt.Sprintf("describe %s", instanceIdArg),
Short: "Describes STACKIT Git instance",
Long: "Describes a STACKIT Git instance by its internal ID.",
Args: args.SingleArg(instanceIdArg, utils.ValidateUUID),
Example: examples.Build(
examples.NewExample(`Describe instance "xxx"`, `$ stackit git describe xxx`),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(params.Printer, cmd, args)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(params.Printer)
if err != nil {
return err
}
// Call API
request := buildRequest(ctx, model, apiClient)
instance, err := request.Execute()
if err != nil {
return fmt.Errorf("get instance: %w", err)
}
if err := outputResult(params.Printer, model.OutputFormat, instance); err != nil {
return err
}
return nil
},
}
return cmd
}
func parseInput(p *print.Printer, cmd *cobra.Command, cliArgs []string) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)
if globalFlags.ProjectId == "" {
return nil, &errors.ProjectIdError{}
}
model := inputModel{
GlobalFlagModel: globalFlags,
InstanceId: cliArgs[0],
}
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 *git.APIClient) git.ApiGetInstanceRequest {
return apiClient.GetInstance(ctx, model.ProjectId, model.InstanceId)
}
func outputResult(p *print.Printer, outputFormat string, resp *git.Instance) error {
if resp == nil {
return fmt.Errorf("instance not found")
}
switch outputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(resp, "", " ")
if err != nil {
return fmt.Errorf("marshal instance: %w", err)
}
p.Outputln(string(details))
return nil
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true), yaml.UseJSONMarshaler())
if err != nil {
return fmt.Errorf("marshal instance: %w", err)
}
p.Outputln(string(details))
return nil
default:
table := tables.NewTable()
if id := resp.Id; id != nil {
table.AddRow("ID", *id)
table.AddSeparator()
}
if name := resp.Name; name != nil {
table.AddRow("NAME", *name)
table.AddSeparator()
}
if url := resp.Url; url != nil {
table.AddRow("URL", *url)
table.AddSeparator()
}
if version := resp.Version; version != nil {
table.AddRow("VERSION", *version)
table.AddSeparator()
}
if state := resp.State; state != nil {
table.AddRow("STATE", *state)
table.AddSeparator()
}
if created := resp.Created; created != nil {
table.AddRow("CREATED", *created)
table.AddSeparator()
}
if err := table.Display(p); err != nil {
return fmt.Errorf("render table: %w", err)
}
return nil
}
}
package list
import (
"context"
"strconv"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/stackitcloud/stackit-cli/internal/cmd/params"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/git"
)
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &git.APIClient{}
var testProjectId = uuid.NewString()
const (
testLimit = 10
)
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
globalflags.ProjectIdFlag: testProjectId,
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
ProjectId: testProjectId,
Verbosity: globalflags.VerbosityDefault,
},
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureRequest(mods ...func(request *git.ApiListInstancesRequest)) git.ApiListInstancesRequest {
request := testClient.ListInstances(testCtx, testProjectId)
for _, mod := range mods {
mod(&request)
}
return request
}
func TestParseInput(t *testing.T) {
tests := []struct {
description string
flagValues map[string]string
isValid bool
expectedModel *inputModel
}{
{
description: "base",
flagValues: fixtureFlagValues(),
isValid: true,
expectedModel: fixtureInputModel(),
},
{
description: "no values",
flagValues: map[string]string{},
isValid: false,
},
{
description: "project id missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, globalflags.ProjectIdFlag)
}),
isValid: false,
},
{
description: "project id invalid 1",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[globalflags.ProjectIdFlag] = ""
}),
isValid: false,
},
{
description: "project id invalid 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[globalflags.ProjectIdFlag] = "invalid-uuid"
}),
isValid: false,
},
{
description: "with limit flag",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues["limit"] = strconv.Itoa(testLimit)
}),
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.Limit = utils.Ptr(int64(testLimit))
}),
},
{
description: "with limit flag == 0",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues["limit"] = strconv.Itoa(0)
}),
isValid: false,
},
{
description: "with limit flag < 0",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues["limit"] = strconv.Itoa(-1)
}),
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
p := print.NewPrinter()
cmd := NewCmd(&params.CmdParams{Printer: p})
err := globalflags.Configure(cmd.Flags())
if err != nil {
t.Fatalf("configure global flags: %v", err)
}
for flag, value := range tt.flagValues {
err := cmd.Flags().Set(flag, value)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("setting flag --%s=%s: %v", flag, value, err)
}
}
err = cmd.ValidateRequiredFlags()
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error validating flags: %v", err)
}
model, err := parseInput(p, cmd)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error parsing flags: %v", err)
}
if !tt.isValid {
t.Fatalf("did not fail on invalid input")
}
diff := cmp.Diff(model, tt.expectedModel)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest git.ApiListInstancesRequest
}{
{
description: "base",
model: fixtureInputModel(),
expectedRequest: fixtureRequest(),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
request := buildRequest(testCtx, tt.model, testClient)
diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmpopts.EquateComparable(testCtx),
)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
func TestOutputResult(t *testing.T) {
type args struct {
outputFormat string
instances []git.Instance
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "empty",
args: args{},
wantErr: false,
},
{
name: "set empty instances slice",
args: args{
instances: []git.Instance{},
},
wantErr: false,
},
{
name: "set empty instances in instances slice",
args: args{
instances: []git.Instance{{}},
},
wantErr: false,
},
}
p := print.NewPrinter()
p.Cmd = NewCmd(&params.CmdParams{Printer: p})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := outputResult(p, tt.args.outputFormat, tt.args.instances); (err != nil) != tt.wantErr {
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
package list
import (
"context"
"encoding/json"
"fmt"
"github.com/stackitcloud/stackit-cli/internal/cmd/params"
"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/flags"
"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/git/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/git"
)
type inputModel struct {
*globalflags.GlobalFlagModel
Limit *int64
}
const limitFlag = "limit"
func NewCmd(params *params.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: "Lists all instances of STACKIT Git.",
Long: "Lists all instances of STACKIT Git for the current project.",
Args: args.NoArgs,
Example: examples.Build(
examples.NewExample(
`List all STACKIT Git instances`,
"$ stackit git instance list"),
examples.NewExample(
"Lists up to 10 STACKIT Git instances",
"$ stackit git instance list --limit=10",
),
),
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := context.Background()
model, err := parseInput(params.Printer, cmd)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(params.Printer)
if err != nil {
return err
}
// Call API
req := buildRequest(ctx, model, apiClient)
resp, err := req.Execute()
if err != nil {
return fmt.Errorf("get STACKIT Git instances: %w", err)
}
instances := *resp.Instances
if len(instances) == 0 {
projectLabel, err := projectname.GetProjectName(ctx, params.Printer, params.CliVersion, cmd)
if err != nil {
params.Printer.Debug(print.ErrorLevel, "get project name: %v", err)
projectLabel = model.ProjectId
}
params.Printer.Info("No instances found for project %q\n", projectLabel)
return nil
} else if model.Limit != nil && len(instances) > int(*model.Limit) {
instances = (instances)[:*model.Limit]
}
return outputResult(params.Printer, model.OutputFormat, instances)
},
}
configureFlags(cmd)
return cmd
}
func configureFlags(cmd *cobra.Command) {
cmd.Flags().Int64(limitFlag, 0, "Limit the output to the first n elements")
}
func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)
if globalFlags.ProjectId == "" {
return nil, &errors.ProjectIdError{}
}
limit := flags.FlagToInt64Pointer(p, cmd, limitFlag)
if limit != nil && *limit < 1 {
return nil, &errors.FlagValidationError{
Flag: limitFlag,
Details: "must be greater than 0",
}
}
model := inputModel{
GlobalFlagModel: globalFlags,
Limit: limit,
}
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 *git.APIClient) git.ApiListInstancesRequest {
return apiClient.ListInstances(ctx, model.ProjectId)
}
func outputResult(p *print.Printer, outputFormat string, instances []git.Instance) error {
switch outputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(instances, "", " ")
if err != nil {
return fmt.Errorf("marshal Observability instance list: %w", err)
}
p.Outputln(string(details))
return nil
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(instances, yaml.IndentSequence(true), yaml.UseJSONMarshaler())
if err != nil {
return fmt.Errorf("marshal Observability instance list: %w", err)
}
p.Outputln(string(details))
return nil
default:
table := tables.NewTable()
table.SetHeader("ID", "NAME", "URL", "VERSION", "STATE", "CREATED")
for i := range instances {
instance := (instances)[i]
table.AddRow(
utils.PtrString(instance.Id),
utils.PtrString(instance.Name),
utils.PtrString(instance.Url),
utils.PtrString(instance.Version),
utils.PtrString(instance.State),
utils.PtrString(instance.Created),
)
}
err := table.Display(p)
if err != nil {
return fmt.Errorf("render table: %w", err)
}
return nil
}
}

Sorry, the diff of this file is too big to display