🚀 Big News:Socket Has Acquired Secure Annex.Learn More
Socket
Book a DemoSign in
Socket

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

Package Overview
Dependencies
Versions
178
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 - go Package Compare versions

Comparing version
v0.6.0
to
v0.7.0
+56
docs/stackit_beta_sqlserverflex_instance_create.md
## stackit beta sqlserverflex instance create
Creates an SQLServer Flex instance
### Synopsis
Creates an SQLServer Flex instance.
```
stackit beta sqlserverflex instance create [flags]
```
### Examples
```
Create an SQLServer Flex instance with name "my-instance" and specify flavor by CPU and RAM. Other parameters are set to default values
$ stackit beta sqlserverflex instance create --name my-instance --cpu 1 --ram 4
Create an SQLServer Flex instance with name "my-instance" and specify flavor by ID. Other parameters are set to default values
$ stackit beta sqlserverflex instance create --name my-instance --flavor-id xxx
Create an SQLServer Flex instance with name "my-instance", specify flavor by CPU and RAM, set storage size to 20 GB, and restrict access to a specific range of IP addresses. Other parameters are set to default values
$ stackit beta sqlserverflex instance create --name my-instance --cpu 1 --ram 4 --storage-size 20 --acl 1.2.3.0/24
```
### Options
```
--acl strings The access control list (ACL). Must contain at least one valid subnet, for instance '0.0.0.0/0' for open access (discouraged), '1.2.3.0/24 for a public IP range of an organization, '1.2.3.4/32' for a single IP range, etc. (default [])
--backup-schedule string Backup schedule
--cpu int Number of CPUs
--edition string Edition of the SQLServer instance
--flavor-id string ID of the flavor
-h, --help Help for "stackit beta sqlserverflex instance create"
-n, --name string Instance name
--ram int Amount of RAM (in GB)
--retention-days int The days for how long the backup files should be stored before being cleaned up
--storage-class string Storage class
--storage-size int Storage size (in GB)
--version string SQLServer version
```
### 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
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```
### SEE ALSO
* [stackit beta sqlserverflex instance](./stackit_beta_sqlserverflex_instance.md) - Provides functionality for SQLServer Flex instances
## stackit beta sqlserverflex instance delete
Deletes an SQLServer Flex instance
### Synopsis
Deletes an SQLServer Flex instance.
```
stackit beta sqlserverflex instance delete INSTANCE_ID [flags]
```
### Examples
```
Delete an SQLServer Flex instance with ID "xxx"
$ stackit beta sqlserverflex instance delete xxx
```
### Options
```
-h, --help Help for "stackit beta sqlserverflex 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
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```
### SEE ALSO
* [stackit beta sqlserverflex instance](./stackit_beta_sqlserverflex_instance.md) - Provides functionality for SQLServer Flex instances
## stackit beta sqlserverflex instance describe
Shows details of an SQLServer Flex instance
### Synopsis
Shows details of an SQLServer Flex instance.
```
stackit beta sqlserverflex instance describe INSTANCE_ID [flags]
```
### Examples
```
Get details of an SQLServer Flex instance with ID "xxx"
$ stackit beta sqlserverflex instance describe xxx
Get details of an SQLServer Flex instance with ID "xxx" in JSON format
$ stackit beta sqlserverflex instance describe xxx --output-format json
```
### Options
```
-h, --help Help for "stackit beta sqlserverflex 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
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```
### SEE ALSO
* [stackit beta sqlserverflex instance](./stackit_beta_sqlserverflex_instance.md) - Provides functionality for SQLServer Flex instances
## stackit beta sqlserverflex instance list
Lists all SQLServer Flex instances
### Synopsis
Lists all SQLServer Flex instances.
```
stackit beta sqlserverflex instance list [flags]
```
### Examples
```
List all SQLServer Flex instances
$ stackit beta sqlserverflex instance list
List all SQLServer Flex instances in JSON format
$ stackit beta sqlserverflex instance list --output-format json
List up to 10 SQLServer Flex instances
$ stackit beta sqlserverflex instance list --limit 10
```
### Options
```
-h, --help Help for "stackit beta sqlserverflex instance list"
--limit int Maximum number of entries to list
```
### Options inherited from parent commands
```
-y, --assume-yes If set, skips all confirmation prompts
--async If set, runs the command asynchronously
-o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"]
-p, --project-id string Project ID
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```
### SEE ALSO
* [stackit beta sqlserverflex instance](./stackit_beta_sqlserverflex_instance.md) - Provides functionality for SQLServer Flex instances
## stackit beta sqlserverflex instance update
Updates an SQLServer Flex instance
### Synopsis
Updates an SQLServer Flex instance.
```
stackit beta sqlserverflex instance update INSTANCE_ID [flags]
```
### Examples
```
Update the name of an SQLServer Flex instance with ID "xxx"
$ stackit beta sqlserverflex instance update xxx --name my-new-name
Update the backup schedule of an SQLServer Flex instance with ID "xxx"
$ stackit beta sqlserverflex instance update xxx --backup-schedule "30 0 * * *"
```
### Options
```
--acl strings Lists of IP networks in CIDR notation which are allowed to access this instance (default [])
--backup-schedule string Backup schedule
--cpu int Number of CPUs
--flavor-id string ID of the flavor
-h, --help Help for "stackit beta sqlserverflex instance update"
-n, --name string Instance name
--ram int Amount of RAM (in GB)
--version string Version
```
### 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
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```
### SEE ALSO
* [stackit beta sqlserverflex instance](./stackit_beta_sqlserverflex_instance.md) - Provides functionality for SQLServer Flex instances
## stackit beta sqlserverflex instance
Provides functionality for SQLServer Flex instances
### Synopsis
Provides functionality for SQLServer Flex instances.
```
stackit beta sqlserverflex instance [flags]
```
### Options
```
-h, --help Help for "stackit beta sqlserverflex 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
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```
### SEE ALSO
* [stackit beta sqlserverflex](./stackit_beta_sqlserverflex.md) - Provides functionality for SQLServer Flex
* [stackit beta sqlserverflex instance create](./stackit_beta_sqlserverflex_instance_create.md) - Creates an SQLServer Flex instance
* [stackit beta sqlserverflex instance delete](./stackit_beta_sqlserverflex_instance_delete.md) - Deletes an SQLServer Flex instance
* [stackit beta sqlserverflex instance describe](./stackit_beta_sqlserverflex_instance_describe.md) - Shows details of an SQLServer Flex instance
* [stackit beta sqlserverflex instance list](./stackit_beta_sqlserverflex_instance_list.md) - Lists all SQLServer Flex instances
* [stackit beta sqlserverflex instance update](./stackit_beta_sqlserverflex_instance_update.md) - Updates an SQLServer Flex instance
## stackit beta sqlserverflex options
Lists SQL Server Flex options
### Synopsis
Lists SQL Server Flex options (flavors, versions and storages for a given flavor)
Pass one or more flags to filter what categories are shown.
```
stackit beta sqlserverflex options [flags]
```
### Examples
```
List SQL Server Flex flavors options
$ stackit sqlserverflex options --flavors
List SQL Server Flex available versions
$ stackit sqlserverflex options --versions
List SQL Server Flex storage options for a given flavor. The flavor ID can be retrieved by running "$ stackit sqlserverflex options --flavors"
$ stackit sqlserverflex options --storages --flavor-id <FLAVOR_ID>
```
### Options
```
--flavor-id string The flavor ID to show storages for. Only relevant when "--storages" is passed
--flavors Lists supported flavors
-h, --help Help for "stackit beta sqlserverflex options"
--storages Lists supported storages for a given flavor
--versions Lists supported versions
```
### 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
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```
### SEE ALSO
* [stackit beta sqlserverflex](./stackit_beta_sqlserverflex.md) - Provides functionality for SQLServer Flex
## stackit beta sqlserverflex
Provides functionality for SQLServer Flex
### Synopsis
Provides functionality for SQLServer Flex.
```
stackit beta sqlserverflex [flags]
```
### Options
```
-h, --help Help for "stackit beta sqlserverflex"
```
### 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
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```
### SEE ALSO
* [stackit beta](./stackit_beta.md) - Contains beta STACKIT CLI commands
* [stackit beta sqlserverflex instance](./stackit_beta_sqlserverflex_instance.md) - Provides functionality for SQLServer Flex instances
* [stackit beta sqlserverflex options](./stackit_beta_sqlserverflex_options.md) - Lists SQL Server Flex options
## stackit beta
Contains beta STACKIT CLI commands
### Synopsis
Contains beta STACKIT CLI commands.
The commands under this group are still in a beta state, and functionality may be incomplete or have breaking changes.
```
stackit beta [flags]
```
### Examples
```
See the currently available beta commands
$ stackit beta --help
Execute a beta command
$ stackit beta MY_COMMAND
```
### Options
```
-h, --help Help for "stackit beta"
```
### 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
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```
### SEE ALSO
* [stackit](./stackit.md) - Manage STACKIT resources using the command line
* [stackit beta sqlserverflex](./stackit_beta_sqlserverflex.md) - Provides functionality for SQLServer Flex
package beta
import (
"fmt"
"github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/spf13/cobra"
)
func NewCmd(p *print.Printer) *cobra.Command {
cmd := &cobra.Command{
Use: "beta",
Short: "Contains beta STACKIT CLI commands",
Long: fmt.Sprintf("%s\n%s",
"Contains beta STACKIT CLI commands.",
"The commands under this group are still in a beta state, and functionality may be incomplete or have breaking changes."),
Args: args.NoArgs,
Run: utils.CmdHelp,
Example: examples.Build(
examples.NewExample(
"See the currently available beta commands",
"$ stackit beta --help"),
examples.NewExample(
"Execute a beta command",
"$ stackit beta MY_COMMAND"),
),
}
addSubcommands(cmd, p)
return cmd
}
func addSubcommands(cmd *cobra.Command, p *print.Printer) {
cmd.AddCommand(sqlserverflex.NewCmd(p))
}
package create
import (
"context"
"fmt"
"testing"
"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/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex"
)
var projectIdFlag = globalflags.ProjectIdFlag
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &sqlserverflex.APIClient{}
type sqlServerFlexClientMocked struct {
listFlavorsFails bool
listFlavorsResp *sqlserverflex.ListFlavorsResponse
listStoragesFails bool
listStoragesResp *sqlserverflex.ListStoragesResponse
}
func (c *sqlServerFlexClientMocked) CreateInstance(ctx context.Context, projectId string) sqlserverflex.ApiCreateInstanceRequest {
return testClient.CreateInstance(ctx, projectId)
}
func (c *sqlServerFlexClientMocked) ListStoragesExecute(_ context.Context, _, _ string) (*sqlserverflex.ListStoragesResponse, error) {
if c.listFlavorsFails {
return nil, fmt.Errorf("list storages failed")
}
return c.listStoragesResp, nil
}
func (c *sqlServerFlexClientMocked) ListFlavorsExecute(_ context.Context, _ string) (*sqlserverflex.ListFlavorsResponse, error) {
if c.listFlavorsFails {
return nil, fmt.Errorf("list flavors failed")
}
return c.listFlavorsResp, nil
}
var testProjectId = uuid.NewString()
var testFlavorId = uuid.NewString()
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
projectIdFlag: testProjectId,
instanceNameFlag: "example-name",
aclFlag: "0.0.0.0/0",
backupScheduleFlag: "0 0/6 * * *",
flavorIdFlag: testFlavorId,
storageClassFlag: "storage-class", // Non-default
storageSizeFlag: "10",
versionFlag: "6.0",
editionFlag: "developer",
retentionDaysFlag: "32",
}
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,
},
InstanceName: utils.Ptr("example-name"),
ACL: utils.Ptr([]string{"0.0.0.0/0"}),
BackupSchedule: utils.Ptr("0 0/6 * * *"),
FlavorId: utils.Ptr(testFlavorId),
StorageClass: utils.Ptr("storage-class"),
StorageSize: utils.Ptr(int64(10)),
Version: utils.Ptr("6.0"),
Edition: utils.Ptr("developer"),
RetentionDays: utils.Ptr(int64(32)),
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureRequest(mods ...func(request *sqlserverflex.ApiCreateInstanceRequest)) sqlserverflex.ApiCreateInstanceRequest {
request := testClient.CreateInstance(testCtx, testProjectId)
request = request.CreateInstancePayload(fixturePayload())
for _, mod := range mods {
mod(&request)
}
return request
}
func fixturePayload(mods ...func(payload *sqlserverflex.CreateInstancePayload)) sqlserverflex.CreateInstancePayload {
payload := sqlserverflex.CreateInstancePayload{
Name: utils.Ptr("example-name"),
Acl: &sqlserverflex.CreateInstancePayloadAcl{Items: utils.Ptr([]string{"0.0.0.0/0"})},
BackupSchedule: utils.Ptr("0 0/6 * * *"),
FlavorId: utils.Ptr(testFlavorId),
Storage: &sqlserverflex.CreateInstancePayloadStorage{
Class: utils.Ptr("storage-class"),
Size: utils.Ptr(int64(10)),
},
Version: utils.Ptr("6.0"),
Options: &sqlserverflex.CreateInstancePayloadOptions{
Edition: utils.Ptr("developer"),
RetentionDays: utils.Ptr("32"),
},
}
for _, mod := range mods {
mod(&payload)
}
return payload
}
func TestParseInput(t *testing.T) {
tests := []struct {
description string
flagValues map[string]string
aclValues []string
isValid bool
expectedModel *inputModel
}{
{
description: "base",
flagValues: fixtureFlagValues(),
isValid: true,
expectedModel: fixtureInputModel(),
},
{
description: "use CPU and RAM",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[cpuFlag] = "2"
flagValues[ramFlag] = "4"
delete(flagValues, flavorIdFlag)
}),
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.FlavorId = nil
model.CPU = utils.Ptr(int64(2))
model.RAM = utils.Ptr(int64(4))
}),
},
{
description: "no values",
flagValues: map[string]string{},
isValid: false,
},
{
description: "project id missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, projectIdFlag)
}),
isValid: false,
},
{
description: "project id invalid 1",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = ""
}),
isValid: false,
},
{
description: "project id invalid 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = "invalid-uuid"
}),
isValid: false,
},
{
description: "invalid with flavor ID, CPU and RAM",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[cpuFlag] = "2"
flagValues[ramFlag] = "4"
}),
isValid: false,
},
{
description: "invalid with flavor ID and CPU",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[cpuFlag] = "2"
}),
isValid: false,
},
{
description: "invalid with CPU only",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[cpuFlag] = "2"
}),
isValid: false,
},
{
description: "no version",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, versionFlag)
}),
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.Version = nil
}),
},
{
description: "repeated acl flags",
flagValues: fixtureFlagValues(),
aclValues: []string{"198.51.100.14/24", "198.51.100.14/32"},
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.ACL = utils.Ptr(
append(*model.ACL, "198.51.100.14/24", "198.51.100.14/32"),
)
}),
},
{
description: "repeated acl flag with list value",
flagValues: fixtureFlagValues(),
aclValues: []string{"198.51.100.14/24,198.51.100.14/32"},
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.ACL = utils.Ptr(
append(*model.ACL, "198.51.100.14/24", "198.51.100.14/32"),
)
}),
},
{
description: "no acls",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, aclFlag)
}),
aclValues: []string{},
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.ACL = nil
}),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
p := print.NewPrinter()
cmd := NewCmd(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)
}
}
for _, value := range tt.aclValues {
err := cmd.Flags().Set(aclFlag, value)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("setting flag --%s=%s: %v", aclFlag, 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 sqlserverflex.ApiCreateInstanceRequest
listFlavorsFails bool
listFlavorsResp *sqlserverflex.ListFlavorsResponse
listStoragesFails bool
listStoragesResp *sqlserverflex.ListStoragesResponse
isValid bool
}{
{
description: "base with flavor ID",
model: fixtureInputModel(),
isValid: true,
expectedRequest: fixtureRequest(),
listFlavorsResp: &sqlserverflex.ListFlavorsResponse{
Flavors: &[]sqlserverflex.InstanceFlavorEntry{
{
Id: utils.Ptr(testFlavorId),
Cpu: utils.Ptr(int64(2)),
Memory: utils.Ptr(int64(4)),
},
},
},
listStoragesResp: &sqlserverflex.ListStoragesResponse{
StorageClasses: &[]string{"storage-class"},
StorageRange: &sqlserverflex.StorageRange{
Min: utils.Ptr(int64(10)),
Max: utils.Ptr(int64(100)),
},
},
},
{
description: "with CPU and RAM",
model: fixtureInputModel(
func(model *inputModel) {
model.FlavorId = nil
model.CPU = utils.Ptr(int64(2))
model.RAM = utils.Ptr(int64(4))
},
),
isValid: true,
expectedRequest: fixtureRequest(),
listFlavorsResp: &sqlserverflex.ListFlavorsResponse{
Flavors: &[]sqlserverflex.InstanceFlavorEntry{
{
Id: utils.Ptr(testFlavorId),
Cpu: utils.Ptr(int64(2)),
Memory: utils.Ptr(int64(4)),
},
{
Id: utils.Ptr("other-flavor"),
Cpu: utils.Ptr(int64(1)),
Memory: utils.Ptr(int64(8)),
},
},
},
listStoragesResp: &sqlserverflex.ListStoragesResponse{
StorageClasses: &[]string{"storage-class"},
StorageRange: &sqlserverflex.StorageRange{
Min: utils.Ptr(int64(10)),
Max: utils.Ptr(int64(100)),
},
},
},
{
description: "get flavors fails",
model: fixtureInputModel(
func(model *inputModel) {
model.FlavorId = nil
model.CPU = utils.Ptr(int64(2))
model.RAM = utils.Ptr(int64(4))
},
),
listFlavorsFails: true,
isValid: false,
},
{
description: "flavor id not found",
model: fixtureInputModel(
func(model *inputModel) {
model.FlavorId = nil
model.CPU = utils.Ptr(int64(5))
model.RAM = utils.Ptr(int64(9))
},
),
listFlavorsResp: &sqlserverflex.ListFlavorsResponse{
Flavors: &[]sqlserverflex.InstanceFlavorEntry{
{
Id: utils.Ptr(testFlavorId),
Cpu: utils.Ptr(int64(2)),
Memory: utils.Ptr(int64(4)),
},
{
Id: utils.Ptr("other-flavor"),
Cpu: utils.Ptr(int64(1)),
Memory: utils.Ptr(int64(8)),
},
},
},
isValid: false,
},
{
description: "get storages fails",
model: fixtureInputModel(
func(model *inputModel) {
model.FlavorId = nil
model.CPU = utils.Ptr(int64(2))
model.RAM = utils.Ptr(int64(4))
},
),
listFlavorsFails: true,
isValid: false,
},
{
description: "invalid storage class",
model: fixtureInputModel(
func(model *inputModel) {
model.StorageClass = utils.Ptr("non-existing-class")
},
),
listFlavorsResp: &sqlserverflex.ListFlavorsResponse{
Flavors: &[]sqlserverflex.InstanceFlavorEntry{
{
Id: utils.Ptr(testFlavorId),
Cpu: utils.Ptr(int64(2)),
Memory: utils.Ptr(int64(4)),
},
},
},
listStoragesResp: &sqlserverflex.ListStoragesResponse{
StorageClasses: &[]string{"storage-class"},
StorageRange: &sqlserverflex.StorageRange{
Min: utils.Ptr(int64(10)),
Max: utils.Ptr(int64(100)),
},
},
isValid: false,
},
{
description: "invalid storage size",
model: fixtureInputModel(
func(model *inputModel) {
model.StorageSize = utils.Ptr(int64(9))
},
),
listFlavorsResp: &sqlserverflex.ListFlavorsResponse{
Flavors: &[]sqlserverflex.InstanceFlavorEntry{
{
Id: utils.Ptr(testFlavorId),
Cpu: utils.Ptr(int64(2)),
Memory: utils.Ptr(int64(4)),
},
},
},
listStoragesResp: &sqlserverflex.ListStoragesResponse{
StorageClasses: &[]string{"storage-class"},
StorageRange: &sqlserverflex.StorageRange{
Min: utils.Ptr(int64(10)),
Max: utils.Ptr(int64(100)),
},
},
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
client := &sqlServerFlexClientMocked{
listFlavorsFails: tt.listFlavorsFails,
listFlavorsResp: tt.listFlavorsResp,
listStoragesFails: tt.listStoragesFails,
listStoragesResp: tt.listStoragesResp,
}
request, err := buildRequest(testCtx, tt.model, client)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error building request: %v", err)
}
diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmpopts.EquateComparable(testCtx),
)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
package create
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/goccy/go-yaml"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
cliErr "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/sqlserverflex/client"
sqlserverflexUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/utils"
"github.com/stackitcloud/stackit-cli/internal/pkg/spinner"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex"
"github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/wait"
)
const (
instanceNameFlag = "name"
aclFlag = "acl"
backupScheduleFlag = "backup-schedule"
flavorIdFlag = "flavor-id"
cpuFlag = "cpu"
ramFlag = "ram"
storageClassFlag = "storage-class"
storageSizeFlag = "storage-size"
versionFlag = "version"
editionFlag = "edition"
retentionDaysFlag = "retention-days"
)
type inputModel struct {
*globalflags.GlobalFlagModel
InstanceName *string
ACL *[]string
BackupSchedule *string
FlavorId *string
CPU *int64
RAM *int64
StorageClass *string
StorageSize *int64
Version *string
Edition *string
RetentionDays *int64
}
func NewCmd(p *print.Printer) *cobra.Command {
cmd := &cobra.Command{
Use: "create",
Short: "Creates an SQLServer Flex instance",
Long: "Creates an SQLServer Flex instance.",
Args: args.NoArgs,
Example: examples.Build(
examples.NewExample(
`Create an SQLServer Flex instance with name "my-instance" and specify flavor by CPU and RAM. Other parameters are set to default values`,
`$ stackit beta sqlserverflex instance create --name my-instance --cpu 1 --ram 4`),
examples.NewExample(
`Create an SQLServer Flex instance with name "my-instance" and specify flavor by ID. Other parameters are set to default values`,
`$ stackit beta sqlserverflex instance create --name my-instance --flavor-id xxx`),
examples.NewExample(
`Create an SQLServer Flex instance with name "my-instance", specify flavor by CPU and RAM, set storage size to 20 GB, and restrict access to a specific range of IP addresses. Other parameters are set to default values`,
`$ stackit beta sqlserverflex instance create --name my-instance --cpu 1 --ram 4 --storage-size 20 --acl 1.2.3.0/24`),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(p, cmd)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(p)
if err != nil {
return err
}
projectLabel, err := projectname.GetProjectName(ctx, p, cmd)
if err != nil {
p.Debug(print.ErrorLevel, "get project name: %v", err)
projectLabel = model.ProjectId
}
if !model.AssumeYes {
prompt := fmt.Sprintf("Are you sure you want to create an SQLServer Flex instance for project %q?", projectLabel)
err = p.PromptForConfirmation(prompt)
if err != nil {
return err
}
}
// Call API
req, err := buildRequest(ctx, model, apiClient)
if err != nil {
return err
}
resp, err := req.Execute()
if err != nil {
return fmt.Errorf("create SQLServer Flex instance: %w", err)
}
instanceId := *resp.Id
// Wait for async operation, if async mode not enabled
if !model.Async {
s := spinner.New(p)
s.Start("Creating instance")
_, err = wait.CreateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId).WaitWithContext(ctx)
if err != nil {
return fmt.Errorf("wait for SQLServer Flex instance creation: %w", err)
}
s.Stop()
}
return outputResult(p, model, projectLabel, resp)
},
}
configureFlags(cmd)
return cmd
}
func configureFlags(cmd *cobra.Command) {
cmd.Flags().StringP(instanceNameFlag, "n", "", "Instance name")
cmd.Flags().Var(flags.CIDRSliceFlag(), aclFlag, "The access control list (ACL). Must contain at least one valid subnet, for instance '0.0.0.0/0' for open access (discouraged), '1.2.3.0/24 for a public IP range of an organization, '1.2.3.4/32' for a single IP range, etc.")
cmd.Flags().String(backupScheduleFlag, "", "Backup schedule")
cmd.Flags().String(flavorIdFlag, "", "ID of the flavor")
cmd.Flags().Int64(cpuFlag, 0, "Number of CPUs")
cmd.Flags().Int64(ramFlag, 0, "Amount of RAM (in GB)")
cmd.Flags().Int64(storageSizeFlag, 0, "Storage size (in GB)")
cmd.Flags().String(storageClassFlag, "", "Storage class")
cmd.Flags().String(versionFlag, "", "SQLServer version")
cmd.Flags().String(editionFlag, "", "Edition of the SQLServer instance")
cmd.Flags().Int64(retentionDaysFlag, 0, "The days for how long the backup files should be stored before being cleaned up")
err := flags.MarkFlagsRequired(cmd, instanceNameFlag)
cobra.CheckErr(err)
}
func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)
if globalFlags.ProjectId == "" {
return nil, &cliErr.ProjectIdError{}
}
flavorId := flags.FlagToStringPointer(p, cmd, flavorIdFlag)
cpu := flags.FlagToInt64Pointer(p, cmd, cpuFlag)
ram := flags.FlagToInt64Pointer(p, cmd, ramFlag)
if flavorId == nil && (cpu == nil || ram == nil) {
return nil, &cliErr.DatabaseInputFlavorError{
Cmd: cmd,
Service: sqlserverflexUtils.ServiceCmd,
}
}
if flavorId != nil && (cpu != nil || ram != nil) {
return nil, &cliErr.DatabaseInputFlavorError{
Cmd: cmd,
Service: sqlserverflexUtils.ServiceCmd,
}
}
model := inputModel{
GlobalFlagModel: globalFlags,
InstanceName: flags.FlagToStringPointer(p, cmd, instanceNameFlag),
ACL: flags.FlagToStringSlicePointer(p, cmd, aclFlag),
BackupSchedule: flags.FlagToStringPointer(p, cmd, backupScheduleFlag),
FlavorId: flavorId,
CPU: cpu,
RAM: ram,
StorageClass: flags.FlagToStringPointer(p, cmd, storageClassFlag),
StorageSize: flags.FlagToInt64Pointer(p, cmd, storageSizeFlag),
Version: flags.FlagToStringPointer(p, cmd, versionFlag),
Edition: flags.FlagToStringPointer(p, cmd, editionFlag),
RetentionDays: flags.FlagToInt64Pointer(p, cmd, retentionDaysFlag),
}
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
}
type sqlServerFlexClient interface {
CreateInstance(ctx context.Context, projectId string) sqlserverflex.ApiCreateInstanceRequest
ListFlavorsExecute(ctx context.Context, projectId string) (*sqlserverflex.ListFlavorsResponse, error)
ListStoragesExecute(ctx context.Context, projectId, flavorId string) (*sqlserverflex.ListStoragesResponse, error)
}
func buildRequest(ctx context.Context, model *inputModel, apiClient sqlServerFlexClient) (sqlserverflex.ApiCreateInstanceRequest, error) {
req := apiClient.CreateInstance(ctx, model.ProjectId)
var flavorId *string
var err error
flavors, err := apiClient.ListFlavorsExecute(ctx, model.ProjectId)
if err != nil {
return req, fmt.Errorf("get SQLServer Flex flavors: %w", err)
}
if model.FlavorId == nil {
flavorId, err = sqlserverflexUtils.LoadFlavorId(*model.CPU, *model.RAM, flavors.Flavors)
if err != nil {
var dsaInvalidPlanError *cliErr.DSAInvalidPlanError
if !errors.As(err, &dsaInvalidPlanError) {
return req, fmt.Errorf("load flavor ID: %w", err)
}
return req, err
}
} else {
err := sqlserverflexUtils.ValidateFlavorId(*model.FlavorId, flavors.Flavors)
if err != nil {
return req, err
}
flavorId = model.FlavorId
}
storages, err := apiClient.ListStoragesExecute(ctx, model.ProjectId, *flavorId)
if err != nil {
return req, fmt.Errorf("get SQLServer Flex storages: %w", err)
}
err = sqlserverflexUtils.ValidateStorage(model.StorageClass, model.StorageSize, storages, *flavorId)
if err != nil {
return req, err
}
var retentionDays *string
if model.RetentionDays != nil {
retentionDays = utils.Ptr(fmt.Sprintf("%d", *model.RetentionDays))
}
req = req.CreateInstancePayload(sqlserverflex.CreateInstancePayload{
Name: model.InstanceName,
Acl: &sqlserverflex.CreateInstancePayloadAcl{Items: model.ACL},
BackupSchedule: model.BackupSchedule,
FlavorId: flavorId,
Storage: &sqlserverflex.CreateInstancePayloadStorage{
Class: model.StorageClass,
Size: model.StorageSize,
},
Version: model.Version,
Options: &sqlserverflex.CreateInstancePayloadOptions{
Edition: model.Edition,
RetentionDays: retentionDays,
},
})
return req, nil
}
func outputResult(p *print.Printer, model *inputModel, projectLabel string, resp *sqlserverflex.CreateInstanceResponse) error {
switch model.OutputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(resp, "", " ")
if err != nil {
return fmt.Errorf("marshal SQLServerFlex instance: %w", err)
}
p.Outputln(string(details))
return nil
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true))
if err != nil {
return fmt.Errorf("marshal SQLServerFlex instance: %w", err)
}
p.Outputln(string(details))
return nil
default:
operationState := "Created"
if model.Async {
operationState = "Triggered creation of"
}
p.Outputf("%s instance for project %q. Instance ID: %s\n", operationState, projectLabel, *resp.Id)
return nil
}
}
package delete
import (
"context"
"testing"
"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/sqlserverflex"
)
var projectIdFlag = globalflags.ProjectIdFlag
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &sqlserverflex.APIClient{}
var testProjectId = uuid.NewString()
var testInstanceId = uuid.NewString()
func fixtureArgValues(mods ...func(argValues []string)) []string {
argValues := []string{
testInstanceId,
}
for _, mod := range mods {
mod(argValues)
}
return argValues
}
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
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 *sqlserverflex.ApiDeleteInstanceRequest)) sqlserverflex.ApiDeleteInstanceRequest {
request := testClient.DeleteInstance(testCtx, testProjectId, testInstanceId)
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: "no values",
argValues: []string{},
flagValues: map[string]string{},
isValid: false,
},
{
description: "no arg values",
argValues: []string{},
flagValues: fixtureFlagValues(),
isValid: false,
},
{
description: "no flag values",
argValues: fixtureArgValues(),
flagValues: map[string]string{},
isValid: false,
},
{
description: "project id missing",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, projectIdFlag)
}),
isValid: false,
},
{
description: "project id invalid 1",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = ""
}),
isValid: false,
},
{
description: "project id invalid 2",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = "invalid-uuid"
}),
isValid: false,
},
{
description: "instance id invalid 1",
argValues: []string{""},
flagValues: fixtureFlagValues(),
isValid: false,
},
{
description: "instance id invalid 2",
argValues: []string{"invalid-uuid"},
flagValues: fixtureFlagValues(),
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
p := print.NewPrinter()
cmd := NewCmd(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.ValidateArgs(tt.argValues)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error validating args: %v", err)
}
err = cmd.ValidateRequiredFlags()
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error validating flags: %v", err)
}
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("Data does not match: %s", diff)
}
})
}
}
func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest sqlserverflex.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/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/sqlserverflex/client"
sqlserverflexUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/utils"
"github.com/stackitcloud/stackit-cli/internal/pkg/spinner"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex"
"github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/wait"
)
const (
instanceIdArg = "INSTANCE_ID"
)
type inputModel struct {
*globalflags.GlobalFlagModel
InstanceId string
}
func NewCmd(p *print.Printer) *cobra.Command {
cmd := &cobra.Command{
Use: fmt.Sprintf("delete %s", instanceIdArg),
Short: "Deletes an SQLServer Flex instance",
Long: "Deletes an SQLServer Flex instance.",
Args: args.SingleArg(instanceIdArg, utils.ValidateUUID),
Example: examples.Build(
examples.NewExample(
`Delete an SQLServer Flex instance with ID "xxx"`,
"$ stackit beta sqlserverflex instance delete xxx"),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(p, cmd, args)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(p)
if err != nil {
return err
}
instanceLabel, err := sqlserverflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId)
if err != nil {
p.Debug(print.ErrorLevel, "get instance name: %v", err)
instanceLabel = model.InstanceId
}
if !model.AssumeYes {
prompt := fmt.Sprintf("Are you sure you want to delete instance %q? (This cannot be undone)", instanceLabel)
err = p.PromptForConfirmation(prompt)
if err != nil {
return err
}
}
// Call API
req := buildRequest(ctx, model, apiClient)
err = req.Execute()
if err != nil {
return fmt.Errorf("delete SQLServer Flex instance: %w", err)
}
// Wait for async operation, if async mode not enabled
if !model.Async {
s := spinner.New(p)
s.Start("Deleting instance")
_, err = wait.DeleteInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId).WaitWithContext(ctx)
if err != nil {
return fmt.Errorf("wait for SQLServer Flex instance deletion: %w", err)
}
s.Stop()
}
operationState := "Deleted"
if model.Async {
operationState = "Triggered deletion of"
}
p.Info("%s instance %q\n", operationState, instanceLabel)
return nil
},
}
return cmd
}
func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) {
instanceId := inputArgs[0]
globalFlags := globalflags.Parse(p, cmd)
if globalFlags.ProjectId == "" {
return nil, &errors.ProjectIdError{}
}
model := inputModel{
GlobalFlagModel: globalFlags,
InstanceId: instanceId,
}
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 *sqlserverflex.APIClient) sqlserverflex.ApiDeleteInstanceRequest {
req := apiClient.DeleteInstance(ctx, model.ProjectId, model.InstanceId)
return req
}
package describe
import (
"context"
"testing"
"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/sqlserverflex"
)
var projectIdFlag = globalflags.ProjectIdFlag
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &sqlserverflex.APIClient{}
var testProjectId = uuid.NewString()
var testInstanceId = uuid.NewString()
func fixtureArgValues(mods ...func(argValues []string)) []string {
argValues := []string{
testInstanceId,
}
for _, mod := range mods {
mod(argValues)
}
return argValues
}
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
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 *sqlserverflex.ApiGetInstanceRequest)) sqlserverflex.ApiGetInstanceRequest {
request := testClient.GetInstance(testCtx, testProjectId, testInstanceId)
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: "no values",
argValues: []string{},
flagValues: map[string]string{},
isValid: false,
},
{
description: "no arg values",
argValues: []string{},
flagValues: fixtureFlagValues(),
isValid: false,
},
{
description: "no flag values",
argValues: fixtureArgValues(),
flagValues: map[string]string{},
isValid: false,
},
{
description: "project id missing",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, projectIdFlag)
}),
isValid: false,
},
{
description: "project id invalid 1",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = ""
}),
isValid: false,
},
{
description: "project id invalid 2",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = "invalid-uuid"
}),
isValid: false,
},
{
description: "instance id invalid 1",
argValues: []string{""},
flagValues: fixtureFlagValues(),
isValid: false,
},
{
description: "instance id invalid 2",
argValues: []string{"invalid-uuid"},
flagValues: fixtureFlagValues(),
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
p := print.NewPrinter()
cmd := NewCmd(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.ValidateArgs(tt.argValues)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error validating args: %v", err)
}
err = cmd.ValidateRequiredFlags()
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error validating flags: %v", err)
}
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("Data does not match: %s", diff)
}
})
}
}
func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest sqlserverflex.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)
}
})
}
}
package describe
import (
"context"
"encoding/json"
"fmt"
"strings"
"github.com/goccy/go-yaml"
"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/sqlserverflex/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex"
)
const (
instanceIdArg = "INSTANCE_ID"
)
type inputModel struct {
*globalflags.GlobalFlagModel
InstanceId string
}
func NewCmd(p *print.Printer) *cobra.Command {
cmd := &cobra.Command{
Use: fmt.Sprintf("describe %s", instanceIdArg),
Short: "Shows details of an SQLServer Flex instance",
Long: "Shows details of an SQLServer Flex instance.",
Args: args.SingleArg(instanceIdArg, utils.ValidateUUID),
Example: examples.Build(
examples.NewExample(
`Get details of an SQLServer Flex instance with ID "xxx"`,
"$ stackit beta sqlserverflex instance describe xxx"),
examples.NewExample(
`Get details of an SQLServer Flex instance with ID "xxx" in JSON format`,
"$ stackit beta sqlserverflex instance describe xxx --output-format json"),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(p, cmd, args)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(p)
if err != nil {
return err
}
// Call API
req := buildRequest(ctx, model, apiClient)
resp, err := req.Execute()
if err != nil {
return fmt.Errorf("read SQLServer Flex instance: %w", err)
}
return outputResult(p, model.OutputFormat, resp.Item)
},
}
return cmd
}
func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) {
instanceId := inputArgs[0]
globalFlags := globalflags.Parse(p, cmd)
if globalFlags.ProjectId == "" {
return nil, &errors.ProjectIdError{}
}
model := inputModel{
GlobalFlagModel: globalFlags,
InstanceId: instanceId,
}
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 *sqlserverflex.APIClient) sqlserverflex.ApiGetInstanceRequest {
req := apiClient.GetInstance(ctx, model.ProjectId, model.InstanceId)
return req
}
func outputResult(p *print.Printer, outputFormat string, instance *sqlserverflex.Instance) error {
switch outputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(instance, "", " ")
if err != nil {
return fmt.Errorf("marshal SQLServer Flex instance: %w", err)
}
p.Outputln(string(details))
return nil
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(instance, yaml.IndentSequence(true))
if err != nil {
return fmt.Errorf("marshal SQLServer Flex instance: %w", err)
}
p.Outputln(string(details))
return nil
default:
aclsArray := *instance.Acl.Items
acls := strings.Join(aclsArray, ",")
table := tables.NewTable()
table.AddRow("ID", *instance.Id)
table.AddSeparator()
table.AddRow("NAME", *instance.Name)
table.AddSeparator()
table.AddRow("STATUS", *instance.Status)
table.AddSeparator()
table.AddRow("STORAGE SIZE (GB)", *instance.Storage.Size)
table.AddSeparator()
table.AddRow("VERSION", *instance.Version)
table.AddSeparator()
table.AddRow("BACKUP SCHEDULE (UTC)", *instance.BackupSchedule)
table.AddSeparator()
table.AddRow("ACL", acls)
table.AddSeparator()
table.AddRow("FLAVOR DESCRIPTION", *instance.Flavor.Description)
table.AddSeparator()
table.AddRow("CPU", *instance.Flavor.Cpu)
table.AddSeparator()
table.AddRow("RAM (GB)", *instance.Flavor.Memory)
table.AddSeparator()
err := table.Display(p)
if err != nil {
return fmt.Errorf("render table: %w", err)
}
return nil
}
}
package instance
import (
"github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/instance/create"
"github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/instance/delete"
"github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/instance/describe"
"github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/instance/list"
"github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/instance/update"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/spf13/cobra"
)
func NewCmd(p *print.Printer) *cobra.Command {
cmd := &cobra.Command{
Use: "instance",
Short: "Provides functionality for SQLServer Flex instances",
Long: "Provides functionality for SQLServer Flex instances.",
Args: args.NoArgs,
Run: utils.CmdHelp,
}
addSubcommands(cmd, p)
return cmd
}
func addSubcommands(cmd *cobra.Command, p *print.Printer) {
cmd.AddCommand(create.NewCmd(p))
cmd.AddCommand(delete.NewCmd(p))
cmd.AddCommand(describe.NewCmd(p))
cmd.AddCommand(list.NewCmd(p))
cmd.AddCommand(update.NewCmd(p))
}
package list
import (
"context"
"testing"
"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/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex"
)
var projectIdFlag = globalflags.ProjectIdFlag
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &sqlserverflex.APIClient{}
var testProjectId = uuid.NewString()
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
projectIdFlag: testProjectId,
limitFlag: "10",
}
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,
},
Limit: utils.Ptr(int64(10)),
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureRequest(mods ...func(request *sqlserverflex.ApiListInstancesRequest)) sqlserverflex.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, projectIdFlag)
}),
isValid: false,
},
{
description: "project id invalid 1",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = ""
}),
isValid: false,
},
{
description: "project id invalid 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = "invalid-uuid"
}),
isValid: false,
},
{
description: "limit invalid",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[limitFlag] = "invalid"
}),
isValid: false,
},
{
description: "limit invalid 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[limitFlag] = "0"
}),
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
cmd := &cobra.Command{}
err := globalflags.Configure(cmd.Flags())
if err != nil {
t.Fatalf("configure global flags: %v", err)
}
configureFlags(cmd)
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)
}
p := print.NewPrinter()
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 sqlserverflex.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)
}
})
}
}
package list
import (
"context"
"encoding/json"
"fmt"
"github.com/goccy/go-yaml"
"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/sqlserverflex/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex"
)
const (
limitFlag = "limit"
)
type inputModel struct {
*globalflags.GlobalFlagModel
Limit *int64
}
func NewCmd(p *print.Printer) *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: "Lists all SQLServer Flex instances",
Long: "Lists all SQLServer Flex instances.",
Args: args.NoArgs,
Example: examples.Build(
examples.NewExample(
`List all SQLServer Flex instances`,
"$ stackit beta sqlserverflex instance list"),
examples.NewExample(
`List all SQLServer Flex instances in JSON format`,
"$ stackit beta sqlserverflex instance list --output-format json"),
examples.NewExample(
`List up to 10 SQLServer Flex instances`,
"$ stackit beta sqlserverflex instance list --limit 10"),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(p, cmd)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(p)
if err != nil {
return err
}
// Call API
req := buildRequest(ctx, model, apiClient)
resp, err := req.Execute()
if err != nil {
return fmt.Errorf("get SQLServer Flex instances: %w", err)
}
if resp.Items == nil || len(*resp.Items) == 0 {
projectLabel, err := projectname.GetProjectName(ctx, p, cmd)
if err != nil {
p.Debug(print.ErrorLevel, "get project name: %v", err)
projectLabel = model.ProjectId
}
p.Info("No instances found for project %q\n", projectLabel)
return nil
}
instances := *resp.Items
// Truncate output
if model.Limit != nil && len(instances) > int(*model.Limit) {
instances = instances[:*model.Limit]
}
return outputResult(p, model.OutputFormat, instances)
},
}
configureFlags(cmd)
return cmd
}
func configureFlags(cmd *cobra.Command) {
cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list")
}
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 *sqlserverflex.APIClient) sqlserverflex.ApiListInstancesRequest {
req := apiClient.ListInstances(ctx, model.ProjectId)
return req
}
func outputResult(p *print.Printer, outputFormat string, instances []sqlserverflex.InstanceListInstance) error {
switch outputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(instances, "", " ")
if err != nil {
return fmt.Errorf("marshal SQLServer Flex instance list: %w", err)
}
p.Outputln(string(details))
return nil
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(instances, yaml.IndentSequence(true))
if err != nil {
return fmt.Errorf("marshal SQLServer Flex instance list: %w", err)
}
p.Outputln(string(details))
return nil
default:
table := tables.NewTable()
table.SetHeader("ID", "NAME", "STATUS")
for i := range instances {
instance := instances[i]
table.AddRow(*instance.Id, *instance.Name, *instance.Status)
}
err := table.Display(p)
if err != nil {
return fmt.Errorf("render table: %w", err)
}
return nil
}
}
package update
import (
"context"
"fmt"
"testing"
"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/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex"
)
var projectIdFlag = globalflags.ProjectIdFlag
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &sqlserverflex.APIClient{}
type sqlServerFlexClientMocked struct {
listFlavorsFails bool
listFlavorsResp *sqlserverflex.ListFlavorsResponse
listStoragesFails bool
listStoragesResp *sqlserverflex.ListStoragesResponse
getInstanceFails bool
getInstanceResp *sqlserverflex.GetInstanceResponse
}
func (c *sqlServerFlexClientMocked) PartialUpdateInstance(ctx context.Context, projectId, instanceId string) sqlserverflex.ApiPartialUpdateInstanceRequest {
return testClient.PartialUpdateInstance(ctx, projectId, instanceId)
}
func (c *sqlServerFlexClientMocked) GetInstanceExecute(_ context.Context, _, _ string) (*sqlserverflex.GetInstanceResponse, error) {
if c.getInstanceFails {
return nil, fmt.Errorf("get instance failed")
}
return c.getInstanceResp, nil
}
func (c *sqlServerFlexClientMocked) ListStoragesExecute(_ context.Context, _, _ string) (*sqlserverflex.ListStoragesResponse, error) {
if c.listFlavorsFails {
return nil, fmt.Errorf("list storages failed")
}
return c.listStoragesResp, nil
}
func (c *sqlServerFlexClientMocked) ListFlavorsExecute(_ context.Context, _ string) (*sqlserverflex.ListFlavorsResponse, error) {
if c.listFlavorsFails {
return nil, fmt.Errorf("list flavors failed")
}
return c.listFlavorsResp, nil
}
var testProjectId = uuid.NewString()
var testInstanceId = uuid.NewString()
var testFlavorId = uuid.NewString()
func fixtureArgValues(mods ...func(argValues []string)) []string {
argValues := []string{
testInstanceId,
}
for _, mod := range mods {
mod(argValues)
}
return argValues
}
func fixtureRequiredFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
projectIdFlag: testProjectId,
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureStandardFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
projectIdFlag: testProjectId,
flavorIdFlag: testFlavorId,
instanceNameFlag: "example-name",
aclFlag: "0.0.0.0/0",
backupScheduleFlag: "0 0 * * *",
versionFlag: "5.0",
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureRequiredInputModel(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 fixtureStandardInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
ProjectId: testProjectId,
Verbosity: globalflags.VerbosityDefault,
},
InstanceId: testInstanceId,
FlavorId: utils.Ptr(testFlavorId),
InstanceName: utils.Ptr("example-name"),
ACL: utils.Ptr([]string{"0.0.0.0/0"}),
BackupSchedule: utils.Ptr("0 0 * * *"),
Version: utils.Ptr("5.0"),
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureRequest(mods ...func(request *sqlserverflex.ApiPartialUpdateInstanceRequest)) sqlserverflex.ApiPartialUpdateInstanceRequest {
request := testClient.PartialUpdateInstance(testCtx, testProjectId, testInstanceId)
request = request.PartialUpdateInstancePayload(sqlserverflex.PartialUpdateInstancePayload{})
for _, mod := range mods {
mod(&request)
}
return request
}
func TestParseInput(t *testing.T) {
tests := []struct {
description string
argValues []string
flagValues map[string]string
aclValues []string
isValid bool
expectedModel *inputModel
}{
{
description: "no values",
argValues: []string{},
flagValues: map[string]string{},
isValid: false,
},
{
description: "no arg values",
argValues: []string{},
flagValues: fixtureRequiredFlagValues(),
isValid: false,
},
{
description: "no flag values",
argValues: fixtureArgValues(),
flagValues: map[string]string{},
isValid: false,
},
{
description: "only instance and project ids",
argValues: fixtureArgValues(),
flagValues: fixtureRequiredFlagValues(),
isValid: false,
},
{
description: "all values with flavor id",
argValues: fixtureArgValues(),
flagValues: fixtureStandardFlagValues(),
isValid: true,
expectedModel: fixtureStandardInputModel(),
},
{
description: "all values with cpu and ram",
argValues: fixtureArgValues(),
flagValues: fixtureStandardFlagValues(func(flagValues map[string]string) {
delete(flagValues, flavorIdFlag)
flagValues[cpuFlag] = "2"
flagValues[ramFlag] = "4"
}),
isValid: true,
expectedModel: fixtureStandardInputModel(func(model *inputModel) {
model.FlavorId = nil
model.CPU = utils.Ptr(int64(2))
model.RAM = utils.Ptr(int64(4))
}),
},
{
description: "project id missing",
argValues: fixtureArgValues(),
flagValues: fixtureRequiredFlagValues(func(flagValues map[string]string) {
delete(flagValues, projectIdFlag)
}),
isValid: false,
},
{
description: "project id invalid 1",
argValues: fixtureArgValues(),
flagValues: fixtureRequiredFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = ""
}),
isValid: false,
},
{
description: "project id invalid 2",
argValues: fixtureArgValues(),
flagValues: fixtureRequiredFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = "invalid-uuid"
}),
isValid: false,
},
{
description: "instance id invalid 1",
argValues: []string{""},
flagValues: fixtureRequiredFlagValues(),
isValid: false,
},
{
description: "instance id invalid 2",
argValues: []string{"invalid-uuid"},
flagValues: fixtureRequiredFlagValues(),
isValid: false,
},
{
description: "invalid with flavor ID, CPU and RAM",
argValues: fixtureArgValues(),
flagValues: fixtureRequiredFlagValues(func(flagValues map[string]string) {
flagValues[flavorIdFlag] = testFlavorId
flagValues[cpuFlag] = "2"
flagValues[ramFlag] = "4"
}),
isValid: false,
},
{
description: "invalid with flavor ID and CPU",
argValues: fixtureArgValues(),
flagValues: fixtureRequiredFlagValues(func(flagValues map[string]string) {
flagValues[flavorIdFlag] = testFlavorId
flagValues[cpuFlag] = "2"
}),
isValid: false,
},
{
description: "no acl flag",
argValues: fixtureArgValues(),
flagValues: fixtureStandardFlagValues(func(flagValues map[string]string) {
delete(flagValues, aclFlag)
}),
isValid: true,
expectedModel: fixtureStandardInputModel(func(model *inputModel) {
model.ACL = nil
}),
},
{
description: "repeated acl flags",
argValues: fixtureArgValues(),
flagValues: fixtureRequiredFlagValues(),
aclValues: []string{"198.51.100.14/24", "198.51.100.14/32"},
isValid: true,
expectedModel: fixtureRequiredInputModel(func(model *inputModel) {
model.ACL = utils.Ptr([]string{"198.51.100.14/24", "198.51.100.14/32"})
}),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
p := print.NewPrinter()
cmd := NewCmd(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)
}
}
for _, value := range tt.aclValues {
err := cmd.Flags().Set(aclFlag, value)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("setting flag --%s=%s: %v", aclFlag, value, err)
}
}
err = cmd.ValidateArgs(tt.argValues)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error validating args: %v", err)
}
err = cmd.ValidateRequiredFlags()
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error validating flags: %v", err)
}
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: %s", diff)
}
})
}
}
func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest sqlserverflex.ApiPartialUpdateInstanceRequest
getInstanceFails bool
getInstanceResp *sqlserverflex.GetInstanceResponse
listFlavorsFails bool
listFlavorsResp *sqlserverflex.ListFlavorsResponse
listStoragesFails bool
listStoragesResp *sqlserverflex.ListStoragesResponse
isValid bool
}{
{
description: "no values",
model: fixtureRequiredInputModel(),
isValid: true,
expectedRequest: fixtureRequest(),
},
{
description: "update flavor from id",
model: fixtureRequiredInputModel(func(model *inputModel) {
model.FlavorId = utils.Ptr(testFlavorId)
}),
isValid: true,
listFlavorsResp: &sqlserverflex.ListFlavorsResponse{
Flavors: &[]sqlserverflex.InstanceFlavorEntry{
{
Id: utils.Ptr(testFlavorId),
Cpu: utils.Ptr(int64(2)),
Memory: utils.Ptr(int64(4)),
},
},
},
expectedRequest: testClient.PartialUpdateInstance(testCtx, testProjectId, testInstanceId).
PartialUpdateInstancePayload(sqlserverflex.PartialUpdateInstancePayload{
FlavorId: utils.Ptr(testFlavorId),
}),
},
{
description: "update flavor from cpu and ram",
model: fixtureRequiredInputModel(func(model *inputModel) {
model.CPU = utils.Ptr(int64(2))
model.RAM = utils.Ptr(int64(4))
}),
isValid: true,
listFlavorsResp: &sqlserverflex.ListFlavorsResponse{
Flavors: &[]sqlserverflex.InstanceFlavorEntry{
{
Id: utils.Ptr(testFlavorId),
Cpu: utils.Ptr(int64(2)),
Memory: utils.Ptr(int64(4)),
},
},
},
expectedRequest: testClient.PartialUpdateInstance(testCtx, testProjectId, testInstanceId).
PartialUpdateInstancePayload(sqlserverflex.PartialUpdateInstancePayload{
FlavorId: utils.Ptr(testFlavorId),
}),
},
{
description: "get flavors fails",
model: fixtureRequiredInputModel(
func(model *inputModel) {
model.CPU = utils.Ptr(int64(2))
model.RAM = utils.Ptr(int64(4))
},
),
listFlavorsFails: true,
isValid: false,
},
{
description: "flavor id not found",
model: fixtureRequiredInputModel(
func(model *inputModel) {
model.CPU = utils.Ptr(int64(5))
model.RAM = utils.Ptr(int64(9))
},
),
listFlavorsResp: &sqlserverflex.ListFlavorsResponse{
Flavors: &[]sqlserverflex.InstanceFlavorEntry{
{
Id: utils.Ptr(testFlavorId),
Cpu: utils.Ptr(int64(2)),
Memory: utils.Ptr(int64(4)),
},
{
Id: utils.Ptr("other-flavor"),
Cpu: utils.Ptr(int64(1)),
Memory: utils.Ptr(int64(8)),
},
},
},
isValid: false,
},
{
description: "get instance fails",
model: fixtureRequiredInputModel(
func(model *inputModel) {
model.FlavorId = nil
model.RAM = utils.Ptr(int64(4))
},
),
getInstanceFails: true,
isValid: false,
},
{
description: "get storages fails",
model: fixtureRequiredInputModel(
func(model *inputModel) {
model.FlavorId = nil
model.CPU = utils.Ptr(int64(2))
model.RAM = utils.Ptr(int64(4))
},
),
listFlavorsFails: true,
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
client := &sqlServerFlexClientMocked{
getInstanceFails: tt.getInstanceFails,
getInstanceResp: tt.getInstanceResp,
listFlavorsFails: tt.listFlavorsFails,
listFlavorsResp: tt.listFlavorsResp,
listStoragesFails: tt.listStoragesFails,
listStoragesResp: tt.listStoragesResp,
}
request, err := buildRequest(testCtx, tt.model, client)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error building request: %v", err)
}
if !tt.isValid {
t.Fatalf("did not fail on invalid input")
}
diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmpopts.EquateComparable(testCtx),
)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
package update
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/goccy/go-yaml"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
cliErr "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/sqlserverflex/client"
sqlserverflexUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/utils"
"github.com/stackitcloud/stackit-cli/internal/pkg/spinner"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex"
"github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/wait"
)
const (
instanceIdArg = "INSTANCE_ID"
instanceNameFlag = "name"
aclFlag = "acl"
backupScheduleFlag = "backup-schedule"
flavorIdFlag = "flavor-id"
cpuFlag = "cpu"
ramFlag = "ram"
versionFlag = "version"
)
type inputModel struct {
*globalflags.GlobalFlagModel
InstanceId string
InstanceName *string
ACL *[]string
BackupSchedule *string
FlavorId *string
CPU *int64
RAM *int64
Version *string
}
func NewCmd(p *print.Printer) *cobra.Command {
cmd := &cobra.Command{
Use: fmt.Sprintf("update %s", instanceIdArg),
Short: "Updates an SQLServer Flex instance",
Long: "Updates an SQLServer Flex instance.",
Example: examples.Build(
examples.NewExample(
`Update the name of an SQLServer Flex instance with ID "xxx"`,
"$ stackit beta sqlserverflex instance update xxx --name my-new-name"),
examples.NewExample(
`Update the backup schedule of an SQLServer Flex instance with ID "xxx"`,
`$ stackit beta sqlserverflex instance update xxx --backup-schedule "30 0 * * *"`),
),
Args: args.SingleArg(instanceIdArg, utils.ValidateUUID),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(p, cmd, args)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(p)
if err != nil {
return err
}
instanceLabel, err := sqlserverflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId)
if err != nil {
p.Debug(print.ErrorLevel, "get instance name: %v", err)
instanceLabel = model.InstanceId
}
if !model.AssumeYes {
prompt := fmt.Sprintf("Are you sure you want to update instance %q?", instanceLabel)
err = p.PromptForConfirmation(prompt)
if err != nil {
return err
}
}
// Call API
req, err := buildRequest(ctx, model, apiClient)
if err != nil {
return err
}
resp, err := req.Execute()
if err != nil {
return fmt.Errorf("update SQLServer Flex instance: %w", err)
}
instanceId := *resp.Item.Id
// Wait for async operation, if async mode not enabled
if !model.Async {
s := spinner.New(p)
s.Start("Updating instance")
_, err = wait.PartialUpdateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId).WaitWithContext(ctx)
if err != nil {
return fmt.Errorf("wait for SQLServer Flex instance update: %w", err)
}
s.Stop()
}
return outputResult(p, model, instanceLabel, resp)
},
}
configureFlags(cmd)
return cmd
}
func configureFlags(cmd *cobra.Command) {
cmd.Flags().StringP(instanceNameFlag, "n", "", "Instance name")
cmd.Flags().Var(flags.CIDRSliceFlag(), aclFlag, "Lists of IP networks in CIDR notation which are allowed to access this instance")
cmd.Flags().String(backupScheduleFlag, "", "Backup schedule")
cmd.Flags().String(flavorIdFlag, "", "ID of the flavor")
cmd.Flags().Int64(cpuFlag, 0, "Number of CPUs")
cmd.Flags().Int64(ramFlag, 0, "Amount of RAM (in GB)")
cmd.Flags().String(versionFlag, "", "Version")
}
func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) {
instanceId := inputArgs[0]
globalFlags := globalflags.Parse(p, cmd)
if globalFlags.ProjectId == "" {
return nil, &cliErr.ProjectIdError{}
}
instanceName := flags.FlagToStringPointer(p, cmd, instanceNameFlag)
flavorId := flags.FlagToStringPointer(p, cmd, flavorIdFlag)
cpu := flags.FlagToInt64Pointer(p, cmd, cpuFlag)
ram := flags.FlagToInt64Pointer(p, cmd, ramFlag)
acl := flags.FlagToStringSlicePointer(p, cmd, aclFlag)
backupSchedule := flags.FlagToStringPointer(p, cmd, backupScheduleFlag)
version := flags.FlagToStringPointer(p, cmd, versionFlag)
if instanceName == nil && flavorId == nil && cpu == nil && ram == nil && acl == nil &&
backupSchedule == nil && version == nil {
return nil, &cliErr.EmptyUpdateError{}
}
if flavorId != nil && (cpu != nil || ram != nil) {
return nil, &cliErr.DatabaseInputFlavorError{
Cmd: cmd,
Service: sqlserverflexUtils.ServiceCmd,
Args: inputArgs,
}
}
model := inputModel{
GlobalFlagModel: globalFlags,
InstanceId: instanceId,
InstanceName: instanceName,
ACL: acl,
BackupSchedule: backupSchedule,
FlavorId: flavorId,
CPU: cpu,
RAM: ram,
Version: version,
}
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
}
type sqlServerFlexClient interface {
PartialUpdateInstance(ctx context.Context, projectId, instanceId string) sqlserverflex.ApiPartialUpdateInstanceRequest
GetInstanceExecute(ctx context.Context, projectId, instanceId string) (*sqlserverflex.GetInstanceResponse, error)
ListFlavorsExecute(ctx context.Context, projectId string) (*sqlserverflex.ListFlavorsResponse, error)
ListStoragesExecute(ctx context.Context, projectId, flavorId string) (*sqlserverflex.ListStoragesResponse, error)
}
func buildRequest(ctx context.Context, model *inputModel, apiClient sqlServerFlexClient) (sqlserverflex.ApiPartialUpdateInstanceRequest, error) {
req := apiClient.PartialUpdateInstance(ctx, model.ProjectId, model.InstanceId)
var flavorId *string
var err error
flavors, err := apiClient.ListFlavorsExecute(ctx, model.ProjectId)
if err != nil {
return req, fmt.Errorf("get SQLServer Flex flavors: %w", err)
}
if model.FlavorId == nil && (model.RAM != nil || model.CPU != nil) {
ram := model.RAM
cpu := model.CPU
if model.RAM == nil || model.CPU == nil {
currentInstance, err := apiClient.GetInstanceExecute(ctx, model.ProjectId, model.InstanceId)
if err != nil {
return req, fmt.Errorf("get SQLServer Flex instance: %w", err)
}
if model.RAM == nil {
ram = currentInstance.Item.Flavor.Memory
}
if model.CPU == nil {
cpu = currentInstance.Item.Flavor.Cpu
}
}
flavorId, err = sqlserverflexUtils.LoadFlavorId(*cpu, *ram, flavors.Flavors)
if err != nil {
var dsaInvalidPlanError *cliErr.DSAInvalidPlanError
if !errors.As(err, &dsaInvalidPlanError) {
return req, fmt.Errorf("load flavor ID: %w", err)
}
return req, err
}
} else if model.FlavorId != nil {
err := sqlserverflexUtils.ValidateFlavorId(*model.FlavorId, flavors.Flavors)
if err != nil {
return req, err
}
flavorId = model.FlavorId
}
var payloadAcl *sqlserverflex.CreateInstancePayloadAcl
if model.ACL != nil {
payloadAcl = &sqlserverflex.CreateInstancePayloadAcl{Items: model.ACL}
}
req = req.PartialUpdateInstancePayload(sqlserverflex.PartialUpdateInstancePayload{
Name: model.InstanceName,
Acl: payloadAcl,
BackupSchedule: model.BackupSchedule,
FlavorId: flavorId,
Version: model.Version,
})
return req, nil
}
func outputResult(p *print.Printer, model *inputModel, instanceLabel string, resp *sqlserverflex.UpdateInstanceResponse) error {
switch model.OutputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(resp, "", " ")
if err != nil {
return fmt.Errorf("marshal update SQLServerFlex instance: %w", err)
}
p.Outputln(string(details))
return nil
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true))
if err != nil {
return fmt.Errorf("marshal update SQLServerFlex instance: %w", err)
}
p.Outputln(string(details))
return nil
default:
operationState := "Updated"
if model.Async {
operationState = "Triggered update of"
}
p.Info("%s instance %q\n", operationState, instanceLabel)
return nil
}
}
package options
import (
"context"
"fmt"
"testing"
"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/google/go-cmp/cmp"
"github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex"
)
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
type sqlServerFlexClientMocked struct {
listFlavorsFails bool
listVersionsFails bool
listStoragesFails bool
listFlavorsCalled bool
listVersionsCalled bool
listStoragesCalled bool
}
func (c *sqlServerFlexClientMocked) ListFlavorsExecute(_ context.Context, _ string) (*sqlserverflex.ListFlavorsResponse, error) {
c.listFlavorsCalled = true
if c.listFlavorsFails {
return nil, fmt.Errorf("list flavors failed")
}
return utils.Ptr(sqlserverflex.ListFlavorsResponse{
Flavors: utils.Ptr([]sqlserverflex.InstanceFlavorEntry{}),
}), nil
}
func (c *sqlServerFlexClientMocked) ListVersionsExecute(_ context.Context, _ string) (*sqlserverflex.ListVersionsResponse, error) {
c.listVersionsCalled = true
if c.listVersionsFails {
return nil, fmt.Errorf("list versions failed")
}
return utils.Ptr(sqlserverflex.ListVersionsResponse{
Versions: utils.Ptr([]string{}),
}), nil
}
func (c *sqlServerFlexClientMocked) ListStoragesExecute(_ context.Context, _, _ string) (*sqlserverflex.ListStoragesResponse, error) {
c.listStoragesCalled = true
if c.listStoragesFails {
return nil, fmt.Errorf("list storages failed")
}
return utils.Ptr(sqlserverflex.ListStoragesResponse{
StorageClasses: utils.Ptr([]string{}),
StorageRange: &sqlserverflex.StorageRange{
Min: utils.Ptr(int64(10)),
Max: utils.Ptr(int64(100)),
},
}), nil
}
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
flavorsFlag: "true",
versionsFlag: "true",
storagesFlag: "true",
flavorIdFlag: "2.4",
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModelAllFalse(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{Verbosity: globalflags.VerbosityDefault},
Flavors: false,
Versions: false,
Storages: false,
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureInputModelAllTrue(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{Verbosity: globalflags.VerbosityDefault},
Flavors: true,
Versions: true,
Storages: true,
FlavorId: utils.Ptr("2.4"),
}
for _, mod := range mods {
mod(model)
}
return model
}
func TestParseInput(t *testing.T) {
tests := []struct {
description string
flagValues map[string]string
isValid bool
expectedModel *inputModel
}{
{
description: "all values",
flagValues: fixtureFlagValues(),
isValid: true,
expectedModel: fixtureInputModelAllTrue(),
},
{
description: "no values",
flagValues: map[string]string{},
isValid: false,
},
{
description: "some values 1",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[storagesFlag] = "false"
delete(flagValues, flavorIdFlag)
}),
isValid: true,
expectedModel: fixtureInputModelAllFalse(func(model *inputModel) {
model.Flavors = true
model.Versions = true
}),
},
{
description: "some values 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, flavorsFlag)
delete(flagValues, versionsFlag)
flagValues[storagesFlag] = "true"
flagValues[flavorIdFlag] = "2.4"
}),
isValid: true,
expectedModel: fixtureInputModelAllFalse(func(model *inputModel) {
model.Storages = true
model.FlavorId = utils.Ptr("2.4")
}),
},
{
description: "storages without flavor-id",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, flavorIdFlag)
}),
isValid: false,
},
{
description: "flavor-id without storage",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, storagesFlag)
}),
isValid: true,
expectedModel: fixtureInputModelAllTrue(func(model *inputModel) {
model.Storages = false
}),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
p := print.NewPrinter()
cmd := NewCmd(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 TestBuildAndExecuteRequest(t *testing.T) {
tests := []struct {
description string
model *inputModel
isValid bool
listFlavorsFails bool
listVersionsFails bool
listStoragesFails bool
expectListFlavorsCalled bool
expectListVersionsCalled bool
expectListStoragesCalled bool
}{
{
description: "all values",
model: fixtureInputModelAllTrue(),
isValid: true,
expectListFlavorsCalled: true,
expectListVersionsCalled: true,
expectListStoragesCalled: true,
},
{
description: "no values",
model: fixtureInputModelAllFalse(),
isValid: true,
expectListFlavorsCalled: false,
expectListVersionsCalled: false,
expectListStoragesCalled: false,
},
{
description: "only flavors",
model: fixtureInputModelAllFalse(func(model *inputModel) { model.Flavors = true }),
isValid: true,
expectListFlavorsCalled: true,
},
{
description: "only versions",
model: fixtureInputModelAllFalse(func(model *inputModel) { model.Versions = true }),
isValid: true,
expectListVersionsCalled: true,
},
{
description: "only storages",
model: fixtureInputModelAllFalse(func(model *inputModel) {
model.Storages = true
model.FlavorId = utils.Ptr("2.4")
}),
isValid: true,
expectListStoragesCalled: true,
},
{
description: "list flavors fails",
model: fixtureInputModelAllTrue(),
isValid: false,
listFlavorsFails: true,
expectListFlavorsCalled: true,
expectListVersionsCalled: false,
expectListStoragesCalled: false,
},
{
description: "list versions fails",
model: fixtureInputModelAllTrue(),
isValid: false,
listVersionsFails: true,
expectListFlavorsCalled: true,
expectListVersionsCalled: true,
expectListStoragesCalled: false,
},
{
description: "list storages fails",
model: fixtureInputModelAllTrue(),
isValid: false,
listStoragesFails: true,
expectListFlavorsCalled: true,
expectListVersionsCalled: true,
expectListStoragesCalled: true,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
p := &print.Printer{}
cmd := NewCmd(p)
p.Cmd = cmd
client := &sqlServerFlexClientMocked{
listFlavorsFails: tt.listFlavorsFails,
listVersionsFails: tt.listVersionsFails,
listStoragesFails: tt.listStoragesFails,
}
err := buildAndExecuteRequest(testCtx, p, tt.model, client)
if err != nil && tt.isValid {
t.Fatalf("error building and executing request: %v", err)
}
if err == nil && !tt.isValid {
t.Fatalf("did not fail on invalid input")
}
if !tt.isValid {
return
}
if tt.expectListFlavorsCalled != client.listFlavorsCalled {
t.Fatalf("expected listFlavorsCalled to be %v, got %v", tt.expectListFlavorsCalled, client.listFlavorsCalled)
}
if tt.expectListVersionsCalled != client.listVersionsCalled {
t.Fatalf("expected listVersionsCalled to be %v, got %v", tt.expectListVersionsCalled, client.listVersionsCalled)
}
if tt.expectListStoragesCalled != client.listStoragesCalled {
t.Fatalf("expected listStoragesCalled to be %v, got %v", tt.expectListStoragesCalled, client.listStoragesCalled)
}
})
}
}
package options
import (
"context"
"encoding/json"
"fmt"
"github.com/goccy/go-yaml"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"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/sqlserverflex/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex"
)
const (
flavorsFlag = "flavors"
versionsFlag = "versions"
storagesFlag = "storages"
flavorIdFlag = "flavor-id"
)
type inputModel struct {
*globalflags.GlobalFlagModel
Flavors bool
Versions bool
Storages bool
FlavorId *string
}
type options struct {
Flavors *[]sqlserverflex.InstanceFlavorEntry `json:"flavors,omitempty"`
Versions *[]string `json:"versions,omitempty"`
Storages *flavorStorages `json:"flavorStorages,omitempty"`
}
type flavorStorages struct {
FlavorId string `json:"flavorId"`
Storages *sqlserverflex.ListStoragesResponse `json:"storages"`
}
func NewCmd(p *print.Printer) *cobra.Command {
cmd := &cobra.Command{
Use: "options",
Short: "Lists SQL Server Flex options",
Long: "Lists SQL Server Flex options (flavors, versions and storages for a given flavor)\nPass one or more flags to filter what categories are shown.",
Args: args.NoArgs,
Example: examples.Build(
examples.NewExample(
`List SQL Server Flex flavors options`,
"$ stackit sqlserverflex options --flavors"),
examples.NewExample(
`List SQL Server Flex available versions`,
"$ stackit sqlserverflex options --versions"),
examples.NewExample(
`List SQL Server Flex storage options for a given flavor. The flavor ID can be retrieved by running "$ stackit sqlserverflex options --flavors"`,
"$ stackit sqlserverflex options --storages --flavor-id <FLAVOR_ID>"),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(p, cmd)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(p)
if err != nil {
return err
}
// Call API
err = buildAndExecuteRequest(ctx, p, model, apiClient)
if err != nil {
return fmt.Errorf("get SQL Server Flex options: %w", err)
}
return nil
},
}
configureFlags(cmd)
return cmd
}
func configureFlags(cmd *cobra.Command) {
cmd.Flags().Bool(flavorsFlag, false, "Lists supported flavors")
cmd.Flags().Bool(versionsFlag, false, "Lists supported versions")
cmd.Flags().Bool(storagesFlag, false, "Lists supported storages for a given flavor")
cmd.Flags().String(flavorIdFlag, "", `The flavor ID to show storages for. Only relevant when "--storages" is passed`)
}
func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)
flavors := flags.FlagToBoolValue(p, cmd, flavorsFlag)
versions := flags.FlagToBoolValue(p, cmd, versionsFlag)
storages := flags.FlagToBoolValue(p, cmd, storagesFlag)
flavorId := flags.FlagToStringPointer(p, cmd, flavorIdFlag)
if !flavors && !versions && !storages {
return nil, fmt.Errorf("%s\n\n%s",
"please specify at least one category for which to list the available options.",
"Get details on the available flags by re-running your command with the --help flag.")
}
if storages && flavorId == nil {
return nil, fmt.Errorf("%s\n\n%s\n%s",
`please specify a flavor ID to show storages for by setting the flag "--flavor-id <FLAVOR_ID>".`,
"You can get the available flavor IDs by running:",
" $ stackit sqlserverflex options --flavors")
}
model := inputModel{
GlobalFlagModel: globalFlags,
Flavors: flavors,
Versions: versions,
Storages: storages,
FlavorId: flags.FlagToStringPointer(p, cmd, flavorIdFlag),
}
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
}
type sqlServerFlexOptionsClient interface {
ListFlavorsExecute(ctx context.Context, projectId string) (*sqlserverflex.ListFlavorsResponse, error)
ListVersionsExecute(ctx context.Context, projectId string) (*sqlserverflex.ListVersionsResponse, error)
ListStoragesExecute(ctx context.Context, projectId, flavorId string) (*sqlserverflex.ListStoragesResponse, error)
}
func buildAndExecuteRequest(ctx context.Context, p *print.Printer, model *inputModel, apiClient sqlServerFlexOptionsClient) error {
var flavors *sqlserverflex.ListFlavorsResponse
var versions *sqlserverflex.ListVersionsResponse
var storages *sqlserverflex.ListStoragesResponse
var err error
if model.Flavors {
flavors, err = apiClient.ListFlavorsExecute(ctx, model.ProjectId)
if err != nil {
return fmt.Errorf("get SQL Server Flex flavors: %w", err)
}
}
if model.Versions {
versions, err = apiClient.ListVersionsExecute(ctx, model.ProjectId)
if err != nil {
return fmt.Errorf("get SQL Server Flex versions: %w", err)
}
}
if model.Storages {
storages, err = apiClient.ListStoragesExecute(ctx, model.ProjectId, *model.FlavorId)
if err != nil {
return fmt.Errorf("get SQL Server Flex storages: %w", err)
}
}
return outputResult(p, model, flavors, versions, storages)
}
func outputResult(p *print.Printer, model *inputModel, flavors *sqlserverflex.ListFlavorsResponse, versions *sqlserverflex.ListVersionsResponse, storages *sqlserverflex.ListStoragesResponse) error {
options := &options{}
if flavors != nil {
options.Flavors = flavors.Flavors
}
if versions != nil {
options.Versions = versions.Versions
}
if storages != nil && model.FlavorId != nil {
options.Storages = &flavorStorages{
FlavorId: *model.FlavorId,
Storages: storages,
}
}
switch model.OutputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(options, "", " ")
if err != nil {
return fmt.Errorf("marshal SQL Server Flex options: %w", err)
}
p.Outputln(string(details))
return nil
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(options, yaml.IndentSequence(true))
if err != nil {
return fmt.Errorf("marshal SQL Server Flex options: %w", err)
}
p.Outputln(string(details))
return nil
default:
return outputResultAsTable(p, model, options)
}
}
func outputResultAsTable(p *print.Printer, model *inputModel, options *options) error {
content := ""
if model.Flavors {
content += renderFlavors(*options.Flavors)
}
if model.Versions {
content += renderVersions(*options.Versions)
}
if model.Storages {
content += renderStorages(options.Storages.Storages)
}
err := p.PagerDisplay(content)
if err != nil {
return fmt.Errorf("display output: %w", err)
}
return nil
}
func renderFlavors(flavors []sqlserverflex.InstanceFlavorEntry) string {
if len(flavors) == 0 {
return ""
}
table := tables.NewTable()
table.SetTitle("Flavors")
table.SetHeader("ID", "CPU", "MEMORY", "DESCRIPTION", "VALID INSTANCE TYPES")
for i := range flavors {
f := flavors[i]
table.AddRow(*f.Id, *f.Cpu, *f.Memory, *f.Description, *f.Categories)
}
return table.Render()
}
func renderVersions(versions []string) string {
if len(versions) == 0 {
return ""
}
table := tables.NewTable()
table.SetTitle("Versions")
table.SetHeader("VERSION")
for i := range versions {
v := versions[i]
table.AddRow(v)
}
return table.Render()
}
func renderStorages(resp *sqlserverflex.ListStoragesResponse) string {
if resp.StorageClasses == nil || len(*resp.StorageClasses) == 0 {
return ""
}
storageClasses := *resp.StorageClasses
table := tables.NewTable()
table.SetTitle("Storages")
table.SetHeader("MINIMUM", "MAXIMUM", "STORAGE CLASS")
for i := range storageClasses {
sc := storageClasses[i]
table.AddRow(*resp.StorageRange.Min, *resp.StorageRange.Max, sc)
}
table.EnableAutoMergeOnColumns(1, 2, 3)
return table.Render()
}
package sqlserverflex
import (
"github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/instance"
"github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/options"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/spf13/cobra"
)
func NewCmd(p *print.Printer) *cobra.Command {
cmd := &cobra.Command{
Use: "sqlserverflex",
Short: "Provides functionality for SQLServer Flex",
Long: "Provides functionality for SQLServer Flex.",
Args: args.NoArgs,
Run: utils.CmdHelp,
}
addSubcommands(cmd, p)
return cmd
}
func addSubcommands(cmd *cobra.Command, p *print.Printer) {
cmd.AddCommand(instance.NewCmd(p))
cmd.AddCommand(options.NewCmd(p))
}
package cmd
import (
"errors"
"testing"
"github.com/spf13/cobra"
pkgErrors "github.com/stackitcloud/stackit-cli/internal/pkg/errors"
)
var cmd *cobra.Command
var service *cobra.Command
var resource *cobra.Command
var operation *cobra.Command
func setupCmd() {
cmd = &cobra.Command{
Use: "stackit",
}
service = &cobra.Command{
Use: "service",
}
resource = &cobra.Command{
Use: "resource",
}
operation = &cobra.Command{
Use: "operation",
}
cmd.AddCommand(service)
service.AddCommand(resource)
resource.AddCommand(operation)
}
func TestBeautifyUnknownAndMissingCommandsError(t *testing.T) {
tests := []struct {
description string
inputError error
command *cobra.Command
expectedMsg string
isNotUnknownFlagError bool
}{
{
description: "root command, extra input is a flag",
inputError: errors.New("unknown flag: --something"),
command: cmd,
expectedMsg: pkgErrors.SUBCOMMAND_MISSING,
},
{
description: "non unknown flag error, return the same",
inputError: errors.New("some error"),
command: cmd,
expectedMsg: "some error",
isNotUnknownFlagError: true,
},
}
setupCmd()
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
actualError := beautifyUnknownAndMissingCommandsError(cmd, tt.inputError)
if tt.isNotUnknownFlagError {
if actualError.Error() != tt.expectedMsg {
t.Fatalf("expected error message to be %s, got %s", tt.expectedMsg, actualError.Error())
}
return
}
appendedErr := pkgErrors.AppendUsageTip(errors.New(tt.expectedMsg), cmd)
if actualError.Error() != appendedErr.Error() {
t.Fatalf("expected error to be %s, got %s", appendedErr.Error(), actualError.Error())
}
})
}
}
package config
import (
"os"
"path/filepath"
"testing"
"github.com/spf13/viper"
)
func TestWrite(t *testing.T) {
tests := []struct {
description string
folderName string
folderExists bool
}{
{
description: "write config file",
folderName: "",
},
{
description: "write config file to new folder",
folderName: "new-folder",
},
{
description: "write config file to existing folder",
folderName: "existing-folder",
folderExists: true,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
configPath := filepath.Join(os.TempDir(), tt.folderName, "config.json")
viper.SetConfigFile(configPath)
folderPath = filepath.Dir(configPath)
if tt.folderExists {
err := os.MkdirAll(folderPath, os.ModePerm)
if err != nil {
t.Fatalf("expected error to be nil, got %v", err)
}
}
err := Write()
if err != nil {
t.Fatalf("expected error to be nil, got %v", err)
}
// Check if the file was created
_, err = os.Stat(configPath)
if os.IsNotExist(err) {
t.Fatalf("expected file to exist, got %v", err)
}
// Delete the file
err = os.Remove(configPath)
if err != nil {
t.Fatalf("expected error to be nil, got %v", err)
}
// Delete the folder
if tt.folderName != "" {
err = os.Remove(folderPath)
if err != nil {
t.Fatalf("expected error to be nil, got %v", err)
}
}
})
}
}
package errors
import (
"errors"
"fmt"
"testing"
"github.com/spf13/cobra"
)
var cmd *cobra.Command
var service *cobra.Command
var resource *cobra.Command
var operation *cobra.Command
func setupCmd() {
cmd = &cobra.Command{
Use: "stackit",
}
service = &cobra.Command{
Use: "service",
}
resource = &cobra.Command{
Use: "resource",
}
operation = &cobra.Command{
Use: "operation",
}
cmd.AddCommand(service)
service.AddCommand(resource)
resource.AddCommand(operation)
}
func setupBetaCmd() {
cmd = &cobra.Command{
Use: "stackit",
}
beta := &cobra.Command{
Use: "beta",
}
service = &cobra.Command{
Use: "service",
}
resource = &cobra.Command{
Use: "resource",
}
operation = &cobra.Command{
Use: "operation",
}
cmd.AddCommand(beta)
beta.AddCommand(service)
service.AddCommand(resource)
resource.AddCommand(operation)
}
func TestSimpleErrors(t *testing.T) {
tests := []struct {
description string
err error
expectedMsg string
}{
{
description: "Test ProjectIdError",
err: &ProjectIdError{},
expectedMsg: MISSING_PROJECT_ID,
},
{
description: "Test EmptyUpdateError",
err: &EmptyUpdateError{},
expectedMsg: EMPTY_UPDATE,
},
{
description: "Test AuthError",
err: &AuthError{},
expectedMsg: FAILED_AUTH,
},
{
description: "Test ActivateServiceAccountError",
err: &ActivateServiceAccountError{},
expectedMsg: FAILED_SERVICE_ACCOUNT_ACTIVATION,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
if tt.err.Error() != tt.expectedMsg {
t.Fatalf("expected error to be %s, got %s", tt.expectedMsg, tt.err.Error())
}
})
}
}
func TestArgusInputPlanError(t *testing.T) {
tests := []struct {
description string
args []string
expectedMsg string
}{
{
description: "base",
args: []string{"arg1", "arg2"},
expectedMsg: fmt.Sprintf(ARGUS_INVALID_INPUT_PLAN, "stackit service resource operation arg1 arg2", "service"),
},
{
description: "no args",
args: []string{},
expectedMsg: fmt.Sprintf(ARGUS_INVALID_INPUT_PLAN, "stackit service resource operation", "service"),
},
}
setupCmd()
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
err := &ArgusInputPlanError{
Cmd: operation,
Args: tt.args,
}
if err.Error() != tt.expectedMsg {
t.Fatalf("expected error to be %s, got %s", tt.expectedMsg, err.Error())
}
})
}
}
func TestArgusInvalidPlanError(t *testing.T) {
tests := []struct {
description string
details string
service string
expectedMsg string
}{
{
description: "base",
details: "details",
service: "service",
expectedMsg: fmt.Sprintf(ARGUS_INVALID_PLAN, "details", "service"),
},
{
description: "no details",
details: "",
service: "service",
expectedMsg: fmt.Sprintf(ARGUS_INVALID_PLAN, "", "service"),
},
{
description: "no service",
details: "details",
service: "",
expectedMsg: fmt.Sprintf(ARGUS_INVALID_PLAN, "details", ""),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
err := &ArgusInvalidPlanError{
Service: tt.service,
Details: tt.details,
}
if err.Error() != tt.expectedMsg {
t.Fatalf("expected error to be %s, got %s", tt.expectedMsg, err.Error())
}
})
}
}
func TestDSAInputPlanError(t *testing.T) {
tests := []struct {
description string
args []string
expectedMsg string
}{
{
description: "base",
args: []string{"arg1", "arg2"},
expectedMsg: fmt.Sprintf(DSA_INVALID_INPUT_PLAN, "stackit service resource operation arg1 arg2", "service"),
},
{
description: "no args",
args: []string{},
expectedMsg: fmt.Sprintf(DSA_INVALID_INPUT_PLAN, "stackit service resource operation", "service"),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
setupCmd()
err := &DSAInputPlanError{
Cmd: operation,
Args: tt.args,
}
if err.Error() != tt.expectedMsg {
t.Fatalf("expected error to be %s, got %s", tt.expectedMsg, err.Error())
}
})
}
}
func TestDSAInvalidPlanError(t *testing.T) {
tests := []struct {
description string
details string
service string
expectedMsg string
}{
{
description: "base",
details: "details",
service: "service",
expectedMsg: fmt.Sprintf(DSA_INVALID_PLAN, "details", "service"),
},
{
description: "no details",
details: "",
service: "service",
expectedMsg: fmt.Sprintf(DSA_INVALID_PLAN, "", "service"),
},
{
description: "no service",
details: "details",
service: "",
expectedMsg: fmt.Sprintf(DSA_INVALID_PLAN, "details", ""),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
err := &DSAInvalidPlanError{
Service: tt.service,
Details: tt.details,
}
if err.Error() != tt.expectedMsg {
t.Fatalf("expected error to be %s, got %s", tt.expectedMsg, err.Error())
}
})
}
}
func TestDatabaseInputFlavorError(t *testing.T) {
tests := []struct {
description string
args []string
service string
expectedMsg string
isBetaCmd bool
}{
{
description: "no service",
args: []string{"arg1", "arg2"},
expectedMsg: fmt.Sprintf(DATABASE_INVALID_INPUT_FLAVOR, "stackit service resource operation arg1 arg2", "service"),
},
{
description: "with service",
args: []string{"arg1", "arg2"},
service: "beta service",
expectedMsg: fmt.Sprintf(DATABASE_INVALID_INPUT_FLAVOR, "stackit beta service resource operation arg1 arg2", "beta service"),
isBetaCmd: true,
},
}
for _, tt := range tests {
if tt.isBetaCmd {
setupBetaCmd()
} else {
setupCmd()
}
t.Run(tt.description, func(t *testing.T) {
err := &DatabaseInputFlavorError{
Cmd: operation,
Args: tt.args,
Service: tt.service,
}
if err.Error() != tt.expectedMsg {
t.Fatalf("expected error to be %s, got %s", tt.expectedMsg, err.Error())
}
})
}
}
func TestDatabaseInvalidFlavorError(t *testing.T) {
tests := []struct {
description string
details string
service string
expectedMsg string
}{
{
description: "base",
details: "details",
service: "service",
expectedMsg: fmt.Sprintf(DATABASE_INVALID_FLAVOR, "details", "service"),
},
{
description: "no details",
details: "",
service: "service",
expectedMsg: fmt.Sprintf(DATABASE_INVALID_FLAVOR, "", "service"),
},
{
description: "no service",
details: "details",
service: "",
expectedMsg: fmt.Sprintf(DATABASE_INVALID_FLAVOR, "details", ""),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
err := &DatabaseInvalidFlavorError{
Service: tt.service,
Details: tt.details,
}
if err.Error() != tt.expectedMsg {
t.Fatalf("expected error to be %s, got %s", tt.expectedMsg, err.Error())
}
})
}
}
func TestDatabaseInvalidStorageError(t *testing.T) {
tests := []struct {
description string
details string
service string
flavorId string
expectedMsg string
}{
{
description: "base",
details: "details",
service: "service",
flavorId: "flavorId",
expectedMsg: fmt.Sprintf(DATABASE_INVALID_STORAGE, "details", "service", "flavorId"),
},
{
description: "no details",
details: "",
service: "service",
flavorId: "flavorId",
expectedMsg: fmt.Sprintf(DATABASE_INVALID_STORAGE, "", "service", "flavorId"),
},
{
description: "no service",
details: "details",
service: "",
flavorId: "flavorId",
expectedMsg: fmt.Sprintf(DATABASE_INVALID_STORAGE, "details", "", "flavorId"),
},
{
description: "no flavorId",
details: "details",
service: "service",
flavorId: "",
expectedMsg: fmt.Sprintf(DATABASE_INVALID_STORAGE, "details", "service", ""),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
err := &DatabaseInvalidStorageError{
Service: tt.service,
Details: tt.details,
FlavorId: tt.flavorId,
}
if err.Error() != tt.expectedMsg {
t.Fatalf("expected error to be %s, got %s", tt.expectedMsg, err.Error())
}
})
}
}
func TestFlagValidationError(t *testing.T) {
tests := []struct {
description string
flag string
details string
expectedMsg string
}{
{
description: "base",
flag: "flag",
details: "details",
expectedMsg: fmt.Sprintf(FLAG_VALIDATION, "flag", "details"),
},
{
description: "no flag",
flag: "",
details: "details",
expectedMsg: fmt.Sprintf(FLAG_VALIDATION, "", "details"),
},
{
description: "no details",
flag: "flag",
details: "",
expectedMsg: fmt.Sprintf(FLAG_VALIDATION, "flag", ""),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
err := &FlagValidationError{
Flag: tt.flag,
Details: tt.details,
}
if err.Error() != tt.expectedMsg {
t.Fatalf("expected error to be %s, got %s", tt.expectedMsg, err.Error())
}
})
}
}
func TestRequiredMutuallyExclusiveFlagsError(t *testing.T) {
tests := []struct {
description string
flags []string
expectedMsg string
}{
{
description: "base",
flags: []string{"flag1", "flag2"},
expectedMsg: fmt.Sprintf(REQUIRED_MUTUALLY_EXCLUSIVE_FLAGS, "flag1, flag2"),
},
{
description: "no flags",
flags: []string{},
expectedMsg: fmt.Sprintf(REQUIRED_MUTUALLY_EXCLUSIVE_FLAGS, ""),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
err := &RequiredMutuallyExclusiveFlagsError{
Flags: tt.flags,
}
if err.Error() != tt.expectedMsg {
t.Fatalf("expected error to be %s, got %s", tt.expectedMsg, err.Error())
}
})
}
}
func TestArgValidationError(t *testing.T) {
tests := []struct {
description string
arg string
details string
expectedMsg string
}{
{
description: "base",
arg: "arg",
details: "details",
expectedMsg: fmt.Sprintf(ARG_VALIDATION, "arg", "details"),
},
{
description: "no arg",
arg: "",
details: "details",
expectedMsg: fmt.Sprintf(ARG_VALIDATION, "", "details"),
},
{
description: "no details",
arg: "arg",
details: "",
expectedMsg: fmt.Sprintf(ARG_VALIDATION, "arg", ""),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
err := &ArgValidationError{
Arg: tt.arg,
Details: tt.details,
}
if err.Error() != tt.expectedMsg {
t.Fatalf("expected error to be %s, got %s", tt.expectedMsg, err.Error())
}
})
}
}
func TestSingleArgExpectedError(t *testing.T) {
tests := []struct {
description string
expected string
count int
expectedMsg string
}{
{
description: "base",
expected: "expected",
count: 1,
expectedMsg: fmt.Sprintf(ARG_MISSING, "expected"),
},
{
description: "multiple",
expected: "expected",
count: 2,
expectedMsg: fmt.Sprintf(SINGLE_ARG_EXPECTED, "expected", 2),
},
}
setupCmd()
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
err := &SingleArgExpectedError{
Expected: tt.expected,
Count: tt.count,
Cmd: operation,
}
appendedErr := AppendUsageTip(errors.New(tt.expectedMsg), operation)
if err.Error() != appendedErr.Error() {
t.Fatalf("expected error to be %s, got %s", tt.expectedMsg, err.Error())
}
})
}
}
func TestSingleOptionalArgExpectedError(t *testing.T) {
tests := []struct {
description string
expected string
count int
expectedMsg string
}{
{
description: "base",
expected: "expected",
count: 1,
expectedMsg: fmt.Sprintf(SINGLE_OPTIONAL_ARG_EXPECTED, "expected", 1),
},
{
description: "multiple",
expected: "expected",
count: 2,
expectedMsg: fmt.Sprintf(SINGLE_OPTIONAL_ARG_EXPECTED, "expected", 2),
},
{
description: "no count",
expected: "expected",
count: 0,
expectedMsg: fmt.Sprintf(SINGLE_OPTIONAL_ARG_EXPECTED, "expected", 0),
},
}
setupCmd()
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
err := &SingleOptionalArgExpectedError{
Expected: tt.expected,
Count: tt.count,
Cmd: operation,
}
appendedErr := AppendUsageTip(errors.New(tt.expectedMsg), operation)
if err.Error() != appendedErr.Error() {
t.Fatalf("expected error to be %s, got %s", tt.expectedMsg, err.Error())
}
})
}
}
func TestInputUnknownError(t *testing.T) {
tests := []struct {
description string
input string
command *cobra.Command
expectedMsg string
}{
{
description: "extra argument, not a subcommand",
input: "extra",
command: operation,
expectedMsg: fmt.Sprintf(ARG_UNKNOWN, "extra"),
},
{
description: "extra subcommand",
input: "extra",
command: service,
expectedMsg: fmt.Sprintf(SUBCOMMAND_UNKNOWN, "extra"),
},
}
setupCmd()
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
err := &InputUnknownError{
ProvidedInput: tt.input,
Cmd: tt.command,
}
appendedErr := AppendUsageTip(errors.New(tt.expectedMsg), tt.command)
if err.Error() != appendedErr.Error() {
t.Fatalf("expected error to be %s, got %s", appendedErr.Error(), err.Error())
}
})
}
}
func TestSubcommandMissingError(t *testing.T) {
tests := []struct {
description string
expectedMsg string
}{
{
description: "base",
expectedMsg: SUBCOMMAND_MISSING,
},
}
setupCmd()
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
err := &SubcommandMissingError{
Cmd: cmd,
}
appendedErr := AppendUsageTip(errors.New(tt.expectedMsg), cmd)
if err.Error() != appendedErr.Error() {
t.Fatalf("expected error to be %s, got %s", tt.expectedMsg, err.Error())
}
})
}
}
func TestAppendUsageTip(t *testing.T) {
tests := []struct {
description string
err error
expectedError error
}{
{
description: "base",
err: fmt.Errorf("error"),
expectedError: fmt.Errorf("%w.\n\n%s", fmt.Errorf("error"), fmt.Sprintf(USAGE_TIP, "stackit")),
},
}
setupCmd()
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
err := AppendUsageTip(tt.err, cmd)
if err.Error() != tt.expectedError.Error() {
t.Fatalf("expected error to be %s, got %s", tt.expectedError, err.Error())
}
})
}
}
package fileutils
import (
"os"
"testing"
)
const outputFilePath = "./testPayload.json"
func TestWriteToFile(t *testing.T) {
tests := []struct {
description string
content string
outputFile string
}{
{
description: "write into file",
content: "Test message",
outputFile: outputFilePath,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
err := WriteToFile(tt.outputFile, tt.content)
if err != nil {
t.Fatalf("unexpected error: %s", err.Error())
}
output, err := os.ReadFile(tt.outputFile)
if err != nil {
t.Fatalf("unexpected error: %s", err.Error())
}
if string(output) != tt.content {
t.Errorf("unexpected output: got %q, want %q", output, tt.content)
}
})
}
// Cleanup
err := os.RemoveAll(outputFilePath)
if err != nil {
t.Errorf("failed cleaning test data")
}
}
package fileutils
import (
"fmt"
"os"
)
func WriteToFile(outputFileName, content string) (err error) {
fo, err := os.Create(outputFileName)
if err != nil {
return fmt.Errorf("create output file: %w", err)
}
defer func() {
tempErr := fo.Close()
if tempErr != nil {
if err != nil {
err = fmt.Errorf("%w; close output file: %w", err, tempErr)
} else {
err = fmt.Errorf("close output file: %w", tempErr)
}
}
}()
_, err = fo.WriteString(content)
if err != nil {
return fmt.Errorf("write content to output file: %w", err)
}
return err
}
package projectname
import (
"context"
"testing"
"github.com/google/uuid"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/stackitcloud/stackit-cli/internal/pkg/config"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
)
var testProjectId = uuid.NewString()
func TestGetProjectName(t *testing.T) {
tests := []struct {
description string
projectName string
projectId string
isValid bool
}{
{
description: "Project name from config",
projectName: "project-name",
projectId: testProjectId,
isValid: true,
},
{
description: "empty project name and id",
projectName: "",
projectId: "",
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
viper.Set(config.ProjectNameKey, tt.projectName)
viper.Set(config.ProjectIdKey, tt.projectId)
defer viper.Reset()
p := print.NewPrinter()
cmd := &cobra.Command{}
projectName, err := GetProjectName(context.Background(), p, cmd)
if err != nil {
if tt.isValid {
t.Fatalf("unexpected error: %v", err)
}
return
}
if !tt.isValid {
t.Fatalf("expected error, got project name %q", projectName)
}
if projectName != tt.projectName {
t.Fatalf("expected project name %q, got %q", tt.projectName, projectName)
}
})
}
}
package client
import (
"github.com/stackitcloud/stackit-cli/internal/pkg/auth"
"github.com/stackitcloud/stackit-cli/internal/pkg/config"
"github.com/stackitcloud/stackit-cli/internal/pkg/errors"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/spf13/viper"
sdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config"
"github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex"
)
func ConfigureClient(p *print.Printer) (*sqlserverflex.APIClient, error) {
var err error
var apiClient *sqlserverflex.APIClient
var cfgOptions []sdkConfig.ConfigurationOption
authCfgOption, err := auth.AuthenticationConfig(p, auth.AuthorizeUser)
if err != nil {
p.Debug(print.ErrorLevel, "configure authentication: %v", err)
return nil, &errors.AuthError{}
}
cfgOptions = append(cfgOptions, authCfgOption, sdkConfig.WithRegion("eu01"))
customEndpoint := viper.GetString(config.SQLServerFlexCustomEndpointKey)
if customEndpoint != "" {
cfgOptions = append(cfgOptions, sdkConfig.WithEndpoint(customEndpoint))
}
if p.IsVerbosityDebug() {
cfgOptions = append(cfgOptions,
sdkConfig.WithMiddleware(print.RequestResponseCapturer(p, nil)),
)
}
apiClient, err = sqlserverflex.NewAPIClient(cfgOptions...)
if err != nil {
p.Debug(print.ErrorLevel, "create new API client: %v", err)
return nil, &errors.AuthError{}
}
return apiClient, nil
}
package utils
import (
"context"
"fmt"
"testing"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/google/go-cmp/cmp"
"github.com/google/uuid"
"github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex"
)
var (
testProjectId = uuid.NewString()
testInstanceId = uuid.NewString()
)
const (
testInstanceName = "instance"
testUserName = "user"
)
type sqlServerFlexClientMocked struct {
listVersionsFails bool
listVersionsResp *sqlserverflex.ListVersionsResponse
getInstanceFails bool
getInstanceResp *sqlserverflex.GetInstanceResponse
getUserFails bool
getUserResp *sqlserverflex.GetUserResponse
listRestoreJobsFails bool
listRestoreJobsResp *sqlserverflex.ListRestoreJobsResponse
}
func (m *sqlServerFlexClientMocked) ListVersionsExecute(_ context.Context, _ string) (*sqlserverflex.ListVersionsResponse, error) {
if m.listVersionsFails {
return nil, fmt.Errorf("could not list versions")
}
return m.listVersionsResp, nil
}
func (m *sqlServerFlexClientMocked) ListRestoreJobsExecute(_ context.Context, _, _ string) (*sqlserverflex.ListRestoreJobsResponse, error) {
if m.listRestoreJobsFails {
return nil, fmt.Errorf("could not list versions")
}
return m.listRestoreJobsResp, nil
}
func (m *sqlServerFlexClientMocked) GetInstanceExecute(_ context.Context, _, _ string) (*sqlserverflex.GetInstanceResponse, error) {
if m.getInstanceFails {
return nil, fmt.Errorf("could not get instance")
}
return m.getInstanceResp, nil
}
func (m *sqlServerFlexClientMocked) GetUserExecute(_ context.Context, _, _, _ string) (*sqlserverflex.GetUserResponse, error) {
if m.getUserFails {
return nil, fmt.Errorf("could not get user")
}
return m.getUserResp, nil
}
func TestValidateStorage(t *testing.T) {
tests := []struct {
description string
storageClass *string
storageSize *int64
storages *sqlserverflex.ListStoragesResponse
isValid bool
}{
{
description: "base",
storageClass: utils.Ptr("foo"),
storageSize: utils.Ptr(int64(10)),
storages: &sqlserverflex.ListStoragesResponse{
StorageClasses: &[]string{"bar-1", "bar-2", "foo"},
StorageRange: &sqlserverflex.StorageRange{
Min: utils.Ptr(int64(5)),
Max: utils.Ptr(int64(20)),
},
},
isValid: true,
},
{
description: "nil response",
storageClass: utils.Ptr("foo"),
storageSize: utils.Ptr(int64(10)),
storages: nil,
isValid: false,
},
{
description: "storage size out of range 1",
storageClass: utils.Ptr("foo"),
storageSize: utils.Ptr(int64(1)),
storages: &sqlserverflex.ListStoragesResponse{
StorageClasses: &[]string{"bar-1", "bar-2", "foo"},
StorageRange: &sqlserverflex.StorageRange{
Min: utils.Ptr(int64(5)),
Max: utils.Ptr(int64(20)),
},
},
isValid: false,
},
{
description: "storage size out of range 2",
storageClass: utils.Ptr("foo"),
storageSize: utils.Ptr(int64(200)),
storages: &sqlserverflex.ListStoragesResponse{
StorageClasses: &[]string{"bar-1", "bar-2", "foo"},
StorageRange: &sqlserverflex.StorageRange{
Min: utils.Ptr(int64(5)),
Max: utils.Ptr(int64(20)),
},
},
isValid: false,
},
{
description: "storage size in range limit 1",
storageClass: utils.Ptr("foo"),
storageSize: utils.Ptr(int64(5)),
storages: &sqlserverflex.ListStoragesResponse{
StorageClasses: &[]string{"bar-1", "bar-2", "foo"},
StorageRange: &sqlserverflex.StorageRange{
Min: utils.Ptr(int64(5)),
Max: utils.Ptr(int64(20)),
},
},
isValid: true,
},
{
description: "storage size in range limit 2",
storageClass: utils.Ptr("foo"),
storageSize: utils.Ptr(int64(20)),
storages: &sqlserverflex.ListStoragesResponse{
StorageClasses: &[]string{"bar-1", "bar-2", "foo"},
StorageRange: &sqlserverflex.StorageRange{
Min: utils.Ptr(int64(5)),
Max: utils.Ptr(int64(20)),
},
},
isValid: true,
},
{
description: "invalid storage",
storageClass: utils.Ptr("foo"),
storageSize: utils.Ptr(int64(10)),
storages: &sqlserverflex.ListStoragesResponse{
StorageClasses: &[]string{"bar-1", "bar-2", "bar-3"},
StorageRange: &sqlserverflex.StorageRange{
Min: utils.Ptr(int64(5)),
Max: utils.Ptr(int64(20)),
},
},
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
err := ValidateStorage(tt.storageClass, tt.storageSize, tt.storages, "flavor-id")
if tt.isValid && err != nil {
t.Fatalf("should not have failed: %v", err)
}
if !tt.isValid && err == nil {
t.Fatalf("should have failed")
}
})
}
}
func TestValidateFlavorId(t *testing.T) {
tests := []struct {
description string
flavorId string
flavors *[]sqlserverflex.InstanceFlavorEntry
isValid bool
}{
{
description: "base",
flavorId: "foo",
flavors: &[]sqlserverflex.InstanceFlavorEntry{
{Id: utils.Ptr("bar-1")},
{Id: utils.Ptr("bar-2")},
{Id: utils.Ptr("foo")},
},
isValid: true,
},
{
description: "nil flavors",
flavorId: "foo",
flavors: nil,
isValid: false,
},
{
description: "no flavors",
flavorId: "foo",
flavors: &[]sqlserverflex.InstanceFlavorEntry{},
isValid: false,
},
{
description: "nil flavor id",
flavorId: "foo",
flavors: &[]sqlserverflex.InstanceFlavorEntry{
{Id: utils.Ptr("bar-1")},
{Id: nil},
{Id: utils.Ptr("foo")},
},
isValid: true,
},
{
description: "invalid flavor",
flavorId: "foo",
flavors: &[]sqlserverflex.InstanceFlavorEntry{
{Id: utils.Ptr("bar-1")},
{Id: utils.Ptr("bar-2")},
{Id: utils.Ptr("bar-3")},
},
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
err := ValidateFlavorId(tt.flavorId, tt.flavors)
if tt.isValid && err != nil {
t.Fatalf("should not have failed: %v", err)
}
if !tt.isValid && err == nil {
t.Fatalf("should have failed")
}
})
}
}
func TestLoadFlavorId(t *testing.T) {
tests := []struct {
description string
cpu int64
ram int64
flavors *[]sqlserverflex.InstanceFlavorEntry
isValid bool
expectedOutput *string
}{
{
description: "base",
cpu: 2,
ram: 4,
flavors: &[]sqlserverflex.InstanceFlavorEntry{
{
Id: utils.Ptr("bar-1"),
Cpu: utils.Ptr(int64(2)),
Memory: utils.Ptr(int64(2)),
},
{
Id: utils.Ptr("bar-2"),
Cpu: utils.Ptr(int64(4)),
Memory: utils.Ptr(int64(4)),
},
{
Id: utils.Ptr("foo"),
Cpu: utils.Ptr(int64(2)),
Memory: utils.Ptr(int64(4)),
},
},
isValid: true,
expectedOutput: utils.Ptr("foo"),
},
{
description: "nil flavors",
cpu: 2,
ram: 4,
flavors: nil,
isValid: false,
},
{
description: "no flavors",
cpu: 2,
ram: 4,
flavors: &[]sqlserverflex.InstanceFlavorEntry{},
isValid: false,
},
{
description: "flavors with details missing",
cpu: 2,
ram: 4,
flavors: &[]sqlserverflex.InstanceFlavorEntry{
{
Id: utils.Ptr("bar-1"),
Cpu: nil,
Memory: nil,
},
{
Id: utils.Ptr("bar-2"),
Cpu: utils.Ptr(int64(4)),
Memory: utils.Ptr(int64(4)),
},
{
Id: utils.Ptr("foo"),
Cpu: utils.Ptr(int64(2)),
Memory: utils.Ptr(int64(4)),
},
},
isValid: true,
expectedOutput: utils.Ptr("foo"),
},
{
description: "match with nil id",
cpu: 2,
ram: 4,
flavors: &[]sqlserverflex.InstanceFlavorEntry{
{
Id: utils.Ptr("bar-1"),
Cpu: utils.Ptr(int64(2)),
Memory: utils.Ptr(int64(2)),
},
{
Id: utils.Ptr("bar-2"),
Cpu: utils.Ptr(int64(4)),
Memory: utils.Ptr(int64(4)),
},
{
Id: nil,
Cpu: utils.Ptr(int64(2)),
Memory: utils.Ptr(int64(4)),
},
},
isValid: false,
},
{
description: "invalid settings",
cpu: 2,
ram: 4,
flavors: &[]sqlserverflex.InstanceFlavorEntry{
{
Id: utils.Ptr("bar-1"),
Cpu: utils.Ptr(int64(2)),
Memory: utils.Ptr(int64(2)),
},
{
Id: utils.Ptr("bar-2"),
Cpu: utils.Ptr(int64(4)),
Memory: utils.Ptr(int64(4)),
},
},
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
output, err := LoadFlavorId(tt.cpu, tt.ram, tt.flavors)
if !tt.isValid {
if err == nil {
t.Fatalf("should have failed")
}
return
}
if err != nil {
t.Fatalf("should not have failed: %v", err)
}
if output == nil {
t.Fatalf("returned nil output")
}
diff := cmp.Diff(output, tt.expectedOutput)
if diff != "" {
t.Fatalf("outputs do not match: %s", diff)
}
})
}
}
func TestGetInstanceName(t *testing.T) {
tests := []struct {
description string
getInstanceFails bool
getInstanceResp *sqlserverflex.GetInstanceResponse
isValid bool
expectedOutput string
}{
{
description: "base",
getInstanceResp: &sqlserverflex.GetInstanceResponse{
Item: &sqlserverflex.Instance{
Name: utils.Ptr(testInstanceName),
},
},
isValid: true,
expectedOutput: testInstanceName,
},
{
description: "get instance fails",
getInstanceFails: true,
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
client := &sqlServerFlexClientMocked{
getInstanceFails: tt.getInstanceFails,
getInstanceResp: tt.getInstanceResp,
}
output, err := GetInstanceName(context.Background(), client, testProjectId, testInstanceId)
if tt.isValid && err != nil {
t.Errorf("failed on valid input")
}
if !tt.isValid && err == nil {
t.Errorf("did not fail on invalid input")
}
if !tt.isValid {
return
}
if output != tt.expectedOutput {
t.Errorf("expected output to be %s, got %s", tt.expectedOutput, output)
}
})
}
}
package utils
import (
"context"
"fmt"
"strings"
"github.com/stackitcloud/stackit-cli/internal/pkg/errors"
"github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex"
)
const (
ServiceCmd = "beta sqlserverflex"
)
type SQLServerFlexClient interface {
ListVersionsExecute(ctx context.Context, projectId string) (*sqlserverflex.ListVersionsResponse, error)
GetInstanceExecute(ctx context.Context, projectId, instanceId string) (*sqlserverflex.GetInstanceResponse, error)
}
func ValidateFlavorId(flavorId string, flavors *[]sqlserverflex.InstanceFlavorEntry) error {
if flavors == nil {
return fmt.Errorf("nil flavors")
}
for _, f := range *flavors {
if f.Id != nil && strings.EqualFold(*f.Id, flavorId) {
return nil
}
}
return &errors.DatabaseInvalidFlavorError{
Service: ServiceCmd,
Details: fmt.Sprintf("You provided flavor ID '%s', which is invalid.", flavorId),
}
}
func ValidateStorage(storageClass *string, storageSize *int64, storages *sqlserverflex.ListStoragesResponse, flavorId string) error {
if storages == nil {
return fmt.Errorf("nil storages")
}
if storageSize != nil {
if *storageSize < *storages.StorageRange.Min || *storageSize > *storages.StorageRange.Max {
return fmt.Errorf("%s", fmt.Sprintf("You provided storage size '%d', which is invalid. The valid range is %d-%d.", *storageSize, *storages.StorageRange.Min, *storages.StorageRange.Max))
}
}
if storageClass == nil {
return nil
}
for _, sc := range *storages.StorageClasses {
if strings.EqualFold(*storageClass, sc) {
return nil
}
}
return &errors.DatabaseInvalidStorageError{
Service: ServiceCmd,
Details: fmt.Sprintf("You provided storage class '%s', which is invalid.", *storageClass),
FlavorId: flavorId,
}
}
func LoadFlavorId(cpu, ram int64, flavors *[]sqlserverflex.InstanceFlavorEntry) (*string, error) {
if flavors == nil {
return nil, fmt.Errorf("nil flavors")
}
availableFlavors := ""
for _, f := range *flavors {
if f.Id == nil || f.Cpu == nil || f.Memory == nil {
continue
}
if *f.Cpu == cpu && *f.Memory == ram {
return f.Id, nil
}
availableFlavors = fmt.Sprintf("%s\n- %d CPU, %d GB RAM", availableFlavors, *f.Cpu, *f.Cpu)
}
return nil, &errors.DatabaseInvalidFlavorError{
Service: ServiceCmd,
Details: "You provided an invalid combination for CPU and RAM.",
}
}
func GetInstanceName(ctx context.Context, apiClient SQLServerFlexClient, projectId, instanceId string) (string, error) {
resp, err := apiClient.GetInstanceExecute(ctx, projectId, instanceId)
if err != nil {
return "", fmt.Errorf("get SQLServer Flex instance: %w", err)
}
return *resp.Item.Name, nil
}
+1
-1

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

- name: Self-hosted Renovate
uses: renovatebot/github-action@v40.1.9
uses: renovatebot/github-action@v40.1.11
with:
configurationFile: .github/renovate.json
token: ${{ secrets.RENOVATE_TOKEN }}

@@ -103,3 +103,3 @@ before:

homepage: "https://github.com/stackitcloud/stackit-cli"
description: "A command-line interface to manage STACKIT resources.\nThis CLI is in a BETA state. More services and functionality will be supported soon."
description: "A command-line interface to manage STACKIT resources.\nThis CLI is in a beta state. More services and functionality will be supported soon."
directory: Formula

@@ -121,3 +121,3 @@ license: "Apache-2.0"

summary: A command-line interface to manage STACKIT resources.
description: "A command-line interface to manage STACKIT resources.\nThis CLI is in a BETA state. More services and functionality will be supported soon."
description: "A command-line interface to manage STACKIT resources.\nThis CLI is in a beta state. More services and functionality will be supported soon."
license: Apache-2.0

@@ -124,0 +124,0 @@ confinement: classic

@@ -23,3 +23,3 @@ ## stackit argus scrape-config generate-payload

Generate a Create payload with default values, and adapt it with custom values for the different configuration options
$ stackit argus scrape-config generate-payload > ./payload.json
$ stackit argus scrape-config generate-payload --file-path ./payload.json
<Modify payload in file, if needed>

@@ -29,5 +29,8 @@ $ stackit argus scrape-config create my-config --payload @./payload.json

Generate an Update payload with the values of an existing configuration named "my-config" for Argus instance xxx, and adapt it with custom values for the different configuration options
$ stackit argus scrape-config generate-payload --job-name my-config --instance-id xxx > ./payload.json
$ stackit argus scrape-config generate-payload --job-name my-config --instance-id xxx --file-path ./payload.json
<Modify payload in file>
$ stackit argus scrape-config update my-config --payload @./payload.json
Generate an Update payload with the values of an existing configuration named "my-config" for Argus instance xxx, and preview it in the terminal
$ stackit argus scrape-config generate-payload --job-name my-config --instance-id xxx
```

@@ -38,2 +41,3 @@

```
-f, --file-path string If set, writes the payload to the given file. If unset, writes the payload to the standard output
-h, --help Help for "stackit argus scrape-config generate-payload"

@@ -40,0 +44,0 @@ --instance-id string Instance ID

@@ -51,3 +51,3 @@ ## stackit auth activate-service-account

* [stackit auth](./stackit_auth.md) - Provides authentication functionality
* [stackit auth](./stackit_auth.md) - Authenticates the STACKIT CLI

@@ -38,3 +38,3 @@ ## stackit auth login

* [stackit auth](./stackit_auth.md) - Provides authentication functionality
* [stackit auth](./stackit_auth.md) - Authenticates the STACKIT CLI
## stackit auth
Provides authentication functionality
Authenticates the STACKIT CLI
### Synopsis
Provides authentication functionality.
Authenticates in the STACKIT CLI.

@@ -9,0 +9,0 @@ ```

@@ -50,2 +50,3 @@ ## stackit config set

--ske-custom-endpoint string SKE API base URL, used in calls to this API
--sqlserverflex-custom-endpoint string SQLServer Flex API base URL, used in calls to this API
```

@@ -52,0 +53,0 @@

@@ -50,2 +50,3 @@ ## stackit config unset

--ske-custom-endpoint SKE API base URL. If unset, uses the default base URL
--sqlserverflex-custom-endpoint SQLServer Flex API base URL. If unset, uses the default base URL
--verbosity Verbosity of the CLI

@@ -52,0 +53,0 @@ ```

## stackit dns record-set list
List DNS record sets
Lists DNS record sets
### Synopsis
List DNS record sets. Successfully deleted record sets are not listed by default.
Lists DNS record sets. Successfully deleted record sets are not listed by default.

@@ -9,0 +9,0 @@ ```

@@ -35,4 +35,4 @@ ## stackit dns record-set

* [stackit dns record-set describe](./stackit_dns_record-set_describe.md) - Shows details of a DNS record set
* [stackit dns record-set list](./stackit_dns_record-set_list.md) - List DNS record sets
* [stackit dns record-set list](./stackit_dns_record-set_list.md) - Lists DNS record sets
* [stackit dns record-set update](./stackit_dns_record-set_update.md) - Updates a DNS record set
## stackit dns zone list
List DNS zones
Lists DNS zones
### Synopsis
List DNS zones. Successfully deleted zones are not listed by default.
Lists DNS zones. Successfully deleted zones are not listed by default.

@@ -9,0 +9,0 @@ ```

@@ -35,4 +35,4 @@ ## stackit dns zone

* [stackit dns zone describe](./stackit_dns_zone_describe.md) - Shows details of a DNS zone
* [stackit dns zone list](./stackit_dns_zone_list.md) - List DNS zones
* [stackit dns zone list](./stackit_dns_zone_list.md) - Lists DNS zones
* [stackit dns zone update](./stackit_dns_zone_update.md) - Updates a DNS zone

@@ -18,3 +18,3 @@ ## stackit load-balancer generate-payload

Generate a payload, and adapt it with custom values for the different configuration options
$ stackit load-balancer generate-payload > ./payload.json
$ stackit load-balancer generate-payload --file-path ./payload.json
<Modify payload in file, if needed>

@@ -24,5 +24,8 @@ $ stackit load-balancer create --payload @./payload.json

Generate a payload with values of an existing load balancer, and adapt it with custom values for the different configuration options
$ stackit load-balancer generate-payload --lb-name xxx > ./payload.json
$ stackit load-balancer generate-payload --lb-name xxx --file-path ./payload.json
<Modify payload in file>
$ stackit load-balancer update xxx --payload @./payload.json
Generate a payload with values of an existing load balancer, and preview it in the terminal
$ stackit load-balancer generate-payload --lb-name xxx
```

@@ -33,4 +36,5 @@

```
-h, --help Help for "stackit load-balancer generate-payload"
-n, --lb-name string If set, generates the payload with the current values of the given load balancer. If unset, generates the payload with empty values
-f, --file-path string If set, writes the payload to the given file. If unset, writes the payload to the standard output
-h, --help Help for "stackit load-balancer generate-payload"
-n, --lb-name string If set, generates the payload with the current values of the given load balancer. If unset, generates the payload with empty values
```

@@ -37,0 +41,0 @@

@@ -7,4 +7,4 @@ ## stackit mongodbflex backup restore

Restores a MongoDB Flex instance from a backup of an instance or clones a MongoDB Flex instance from a point-in-time snapshot.
The backup is specified by a backup ID and the point-in-time snapshot is specified by a timestamp.
Restores a MongoDB Flex instance from a backup of an instance or clones a MongoDB Flex instance from a point-in-time backup.
The backup can be specified by a backup ID or a timestamp.
You can specify the instance to which the backup will be applied. If not specified, the backup will be applied to the same instance from which it was taken.

@@ -36,3 +36,3 @@

--instance-id string Instance ID
--timestamp string Timestamp of the snapshot to use as a source for cloning the instance in a date-time with the RFC3339 layout format, e.g. 2024-01-01T00:00:00Z
--timestamp string Timestamp to restore the instance to, in a date-time with the RFC3339 layout format, e.g. 2024-01-01T00:00:00Z
```

@@ -39,0 +39,0 @@

@@ -22,4 +22,4 @@ ## stackit mongodbflex backup update-schedule

Update the retention days for snapshots of a MongoDB Flex instance with ID "xxx" to 5 days
$ stackit mongodbflex backup update-schedule --instance-id xxx --save-snapshot-days 5
Update the retention days for backups of a MongoDB Flex instance with ID "xxx" to 5 days
$ stackit mongodbflex backup update-schedule --instance-id xxx --store-for-days 5
```

@@ -32,7 +32,7 @@

--instance-id string Instance ID
--save-daily-snapshot-days int Number of days to retain daily snapshots. Should be less than or equal to the number of days of the selected weekly or monthly value.
--save-monthly-snapshot-months int Number of months to retain monthly snapshots
--save-snapshot-days int Number of days to retain snapshots. Should be less than or equal to the value of the daily backup.
--save-weekly-snapshot-weeks int Number of weeks to retain weekly snapshots. Should be less than or equal to the number of weeks of the selected monthly value.
--schedule string Backup schedule, in the cron scheduling system format e.g. '0 0 * * *'
--store-daily-backup-days int Number of days to retain daily backups. Should be less than or equal to the number of days of the selected weekly or monthly value.
--store-for-days int Number of days to retain backups. Should be less than or equal to the value of the daily backup.
--store-monthly-backups-months int Number of months to retain monthly backups
--store-weekly-backup-weeks int Number of weeks to retain weekly backups. Should be less than or equal to the number of weeks of the selected monthly value.
```

@@ -39,0 +39,0 @@

@@ -31,3 +31,3 @@ ## stackit object-storage bucket

* [stackit object-storage](./stackit_object-storage.md) - Provides functionality regarding Object Storage
* [stackit object-storage](./stackit_object-storage.md) - Provides functionality for Object Storage
* [stackit object-storage bucket create](./stackit_object-storage_bucket_create.md) - Creates an Object Storage bucket

@@ -34,0 +34,0 @@ * [stackit object-storage bucket delete](./stackit_object-storage_bucket_delete.md) - Deletes an Object Storage bucket

@@ -31,3 +31,3 @@ ## stackit object-storage credentials-group

* [stackit object-storage](./stackit_object-storage.md) - Provides functionality regarding Object Storage
* [stackit object-storage](./stackit_object-storage.md) - Provides functionality for Object Storage
* [stackit object-storage credentials-group create](./stackit_object-storage_credentials-group_create.md) - Creates a credentials group to hold Object Storage access credentials

@@ -34,0 +34,0 @@ * [stackit object-storage credentials-group delete](./stackit_object-storage_credentials-group_delete.md) - Deletes a credentials group that holds Object Storage access credentials

@@ -31,3 +31,3 @@ ## stackit object-storage credentials

* [stackit object-storage](./stackit_object-storage.md) - Provides functionality regarding Object Storage
* [stackit object-storage](./stackit_object-storage.md) - Provides functionality for Object Storage
* [stackit object-storage credentials create](./stackit_object-storage_credentials_create.md) - Creates credentials for an Object Storage credentials group

@@ -34,0 +34,0 @@ * [stackit object-storage credentials delete](./stackit_object-storage_credentials_delete.md) - Deletes credentials of an Object Storage credentials group

@@ -38,3 +38,3 @@ ## stackit object-storage disable

* [stackit object-storage](./stackit_object-storage.md) - Provides functionality regarding Object Storage
* [stackit object-storage](./stackit_object-storage.md) - Provides functionality for Object Storage

@@ -38,3 +38,3 @@ ## stackit object-storage enable

* [stackit object-storage](./stackit_object-storage.md) - Provides functionality regarding Object Storage
* [stackit object-storage](./stackit_object-storage.md) - Provides functionality for Object Storage
## stackit object-storage
Provides functionality regarding Object Storage
Provides functionality for Object Storage
### Synopsis
Provides functionality regarding Object Storage.
Provides functionality for Object Storage.

@@ -9,0 +9,0 @@ ```

@@ -44,3 +44,3 @@ ## stackit organization member add

* [stackit organization member](./stackit_organization_member.md) - Provides functionality regarding organization members
* [stackit organization member](./stackit_organization_member.md) - Manages organization members

@@ -48,3 +48,3 @@ ## stackit organization member list

* [stackit organization member](./stackit_organization_member.md) - Provides functionality regarding organization members
* [stackit organization member](./stackit_organization_member.md) - Manages organization members

@@ -46,3 +46,3 @@ ## stackit organization member remove

* [stackit organization member](./stackit_organization_member.md) - Provides functionality regarding organization members
* [stackit organization member](./stackit_organization_member.md) - Manages organization members
## stackit organization member
Provides functionality regarding organization members
Manages organization members
### Synopsis
Provides functionality regarding organization members.
Manages organization members.

@@ -31,3 +31,3 @@ ```

* [stackit organization](./stackit_organization.md) - Provides functionality regarding organizations
* [stackit organization](./stackit_organization.md) - Manages organizations
* [stackit organization member add](./stackit_organization_member_add.md) - Adds a member to an organization

@@ -34,0 +34,0 @@ * [stackit organization member list](./stackit_organization_member_list.md) - Lists members of an organization

@@ -46,3 +46,3 @@ ## stackit organization role list

* [stackit organization role](./stackit_organization_role.md) - Provides functionality regarding organization roles
* [stackit organization role](./stackit_organization_role.md) - Manages organization roles
## stackit organization role
Provides functionality regarding organization roles
Manages organization roles
### Synopsis
Provides functionality regarding organization roles.
Manages organization roles.

@@ -31,4 +31,4 @@ ```

* [stackit organization](./stackit_organization.md) - Provides functionality regarding organizations
* [stackit organization](./stackit_organization.md) - Manages organizations
* [stackit organization role list](./stackit_organization_role_list.md) - Lists roles and permissions of an organization
## stackit organization
Provides functionality regarding organizations
Manages organizations
### Synopsis
Provides functionality regarding organizations.
Manages organizations.
An active STACKIT organization is the root element of the resource hierarchy and a prerequisite to use any STACKIT Cloud Resource / Service.

@@ -33,4 +33,4 @@

* [stackit](./stackit.md) - Manage STACKIT resources using the command line
* [stackit organization member](./stackit_organization_member.md) - Provides functionality regarding organization members
* [stackit organization role](./stackit_organization_role.md) - Provides functionality regarding organization roles
* [stackit organization member](./stackit_organization_member.md) - Manages organization members
* [stackit organization role](./stackit_organization_role.md) - Manages organization roles

@@ -44,3 +44,3 @@ ## stackit project create

* [stackit project](./stackit_project.md) - Provides functionality regarding projects
* [stackit project](./stackit_project.md) - Manages projects

@@ -41,3 +41,3 @@ ## stackit project delete

* [stackit project](./stackit_project.md) - Provides functionality regarding projects
* [stackit project](./stackit_project.md) - Manages projects

@@ -45,3 +45,3 @@ ## stackit project describe

* [stackit project](./stackit_project.md) - Provides functionality regarding projects
* [stackit project](./stackit_project.md) - Manages projects

@@ -53,3 +53,3 @@ ## stackit project list

* [stackit project](./stackit_project.md) - Provides functionality regarding projects
* [stackit project](./stackit_project.md) - Manages projects

@@ -43,3 +43,3 @@ ## stackit project member add

* [stackit project member](./stackit_project_member.md) - Provides functionality regarding project members
* [stackit project member](./stackit_project_member.md) - Manages project members

@@ -47,3 +47,3 @@ ## stackit project member list

* [stackit project member](./stackit_project_member.md) - Provides functionality regarding project members
* [stackit project member](./stackit_project_member.md) - Manages project members

@@ -45,3 +45,3 @@ ## stackit project member remove

* [stackit project member](./stackit_project_member.md) - Provides functionality regarding project members
* [stackit project member](./stackit_project_member.md) - Manages project members
## stackit project member
Provides functionality regarding project members
Manages project members
### Synopsis
Provides functionality regarding project members.
Manages project members.

@@ -31,3 +31,3 @@ ```

* [stackit project](./stackit_project.md) - Provides functionality regarding projects
* [stackit project](./stackit_project.md) - Manages projects
* [stackit project member add](./stackit_project_member_add.md) - Adds a member to a project

@@ -34,0 +34,0 @@ * [stackit project member list](./stackit_project_member_list.md) - Lists members of a project

@@ -45,3 +45,3 @@ ## stackit project role list

* [stackit project role](./stackit_project_role.md) - Provides functionality regarding project roles
* [stackit project role](./stackit_project_role.md) - Manages project roles
## stackit project role
Provides functionality regarding project roles
Manages project roles
### Synopsis
Provides functionality regarding project roles.
Manages project roles.

@@ -31,4 +31,4 @@ ```

* [stackit project](./stackit_project.md) - Provides functionality regarding projects
* [stackit project](./stackit_project.md) - Manages projects
* [stackit project role list](./stackit_project_role_list.md) - Lists roles and permissions of a project

@@ -47,3 +47,3 @@ ## stackit project update

* [stackit project](./stackit_project.md) - Provides functionality regarding projects
* [stackit project](./stackit_project.md) - Manages projects
## stackit project
Provides functionality regarding projects
Manages projects
### Synopsis
Provides functionality regarding projects.
Provides functionality for projects.
A project is a container for resources which is the service that you can purchase from STACKIT.

@@ -37,5 +37,5 @@

* [stackit project list](./stackit_project_list.md) - Lists STACKIT projects
* [stackit project member](./stackit_project_member.md) - Provides functionality regarding project members
* [stackit project role](./stackit_project_role.md) - Provides functionality regarding project roles
* [stackit project member](./stackit_project_member.md) - Manages project members
* [stackit project role](./stackit_project_role.md) - Manages project roles
* [stackit project update](./stackit_project_update.md) - Updates a STACKIT project

@@ -49,3 +49,3 @@ ## stackit service-account key create

* [stackit service-account key](./stackit_service-account_key.md) - Provides functionality regarding service account keys
* [stackit service-account key](./stackit_service-account_key.md) - Provides functionality for service account keys

@@ -39,3 +39,3 @@ ## stackit service-account key delete

* [stackit service-account key](./stackit_service-account_key.md) - Provides functionality regarding service account keys
* [stackit service-account key](./stackit_service-account_key.md) - Provides functionality for service account keys

@@ -39,3 +39,3 @@ ## stackit service-account key describe

* [stackit service-account key](./stackit_service-account_key.md) - Provides functionality regarding service account keys
* [stackit service-account key](./stackit_service-account_key.md) - Provides functionality for service account keys

@@ -46,3 +46,3 @@ ## stackit service-account key list

* [stackit service-account key](./stackit_service-account_key.md) - Provides functionality regarding service account keys
* [stackit service-account key](./stackit_service-account_key.md) - Provides functionality for service account keys

@@ -49,3 +49,3 @@ ## stackit service-account key update

* [stackit service-account key](./stackit_service-account_key.md) - Provides functionality regarding service account keys
* [stackit service-account key](./stackit_service-account_key.md) - Provides functionality for service account keys
## stackit service-account key
Provides functionality regarding service account keys
Provides functionality for service account keys
### Synopsis
Provides functionality regarding service account keys.
Provides functionality for service account keys.

@@ -9,0 +9,0 @@ ```

@@ -45,3 +45,3 @@ ## stackit service-account token create

* [stackit service-account token](./stackit_service-account_token.md) - Provides functionality regarding service account tokens
* [stackit service-account token](./stackit_service-account_token.md) - Provides functionality for service account tokens

@@ -48,3 +48,3 @@ ## stackit service-account token list

* [stackit service-account token](./stackit_service-account_token.md) - Provides functionality regarding service account tokens
* [stackit service-account token](./stackit_service-account_token.md) - Provides functionality for service account tokens

@@ -41,3 +41,3 @@ ## stackit service-account token revoke

* [stackit service-account token](./stackit_service-account_token.md) - Provides functionality regarding service account tokens
* [stackit service-account token](./stackit_service-account_token.md) - Provides functionality for service account tokens
## stackit service-account token
Provides functionality regarding service account tokens
Provides functionality for service account tokens
### Synopsis
Provides functionality regarding service account tokens.
Provides functionality for service account tokens.

@@ -9,0 +9,0 @@ ```

@@ -35,5 +35,5 @@ ## stackit service-account

* [stackit service-account get-jwks](./stackit_service-account_get-jwks.md) - Shows the JWKS for a service account
* [stackit service-account key](./stackit_service-account_key.md) - Provides functionality regarding service account keys
* [stackit service-account key](./stackit_service-account_key.md) - Provides functionality for service account keys
* [stackit service-account list](./stackit_service-account_list.md) - Lists all service accounts
* [stackit service-account token](./stackit_service-account_token.md) - Provides functionality regarding service account tokens
* [stackit service-account token](./stackit_service-account_token.md) - Provides functionality for service account tokens

@@ -18,3 +18,3 @@ ## stackit ske cluster generate-payload

Generate a payload with default values, and adapt it with custom values for the different configuration options
$ stackit ske cluster generate-payload > ./payload.json
$ stackit ske cluster generate-payload --file-path ./payload.json
<Modify payload in file, if needed>

@@ -24,5 +24,8 @@ $ stackit ske cluster create my-cluster --payload @./payload.json

Generate a payload with values of a cluster, and adapt it with custom values for the different configuration options
$ stackit ske cluster generate-payload --cluster-name my-cluster > ./payload.json
$ stackit ske cluster generate-payload --cluster-name my-cluster --file-path ./payload.json
<Modify payload in file>
$ stackit ske cluster update my-cluster --payload @./payload.json
Generate a payload with values of a cluster, and preview it in the terminal
$ stackit ske cluster generate-payload --cluster-name my-cluster
```

@@ -34,2 +37,3 @@

-n, --cluster-name string If set, generates the payload with the current state of the given cluster. If unset, generates the payload with default values
-f, --file-path string If set, writes the payload to the given file. If unset, writes the payload to the standard output
-h, --help Help for "stackit ske cluster generate-payload"

@@ -36,0 +40,0 @@ ```

@@ -30,3 +30,4 @@ ## stackit

* [stackit argus](./stackit_argus.md) - Provides functionality for Argus
* [stackit auth](./stackit_auth.md) - Provides authentication functionality
* [stackit auth](./stackit_auth.md) - Authenticates the STACKIT CLI
* [stackit beta](./stackit_beta.md) - Contains beta STACKIT CLI commands
* [stackit config](./stackit_config.md) - Provides functionality for CLI configuration options

@@ -39,7 +40,7 @@ * [stackit curl](./stackit_curl.md) - Executes an authenticated HTTP request to an endpoint

* [stackit mongodbflex](./stackit_mongodbflex.md) - Provides functionality for MongoDB Flex
* [stackit object-storage](./stackit_object-storage.md) - Provides functionality regarding Object Storage
* [stackit object-storage](./stackit_object-storage.md) - Provides functionality for Object Storage
* [stackit opensearch](./stackit_opensearch.md) - Provides functionality for OpenSearch
* [stackit organization](./stackit_organization.md) - Provides functionality regarding organizations
* [stackit organization](./stackit_organization.md) - Manages organizations
* [stackit postgresflex](./stackit_postgresflex.md) - Provides functionality for PostgreSQL Flex
* [stackit project](./stackit_project.md) - Provides functionality regarding projects
* [stackit project](./stackit_project.md) - Manages projects
* [stackit rabbitmq](./stackit_rabbitmq.md) - Provides functionality for RabbitMQ

@@ -46,0 +47,0 @@ * [stackit redis](./stackit_redis.md) - Provides functionality for Redis

+8
-7

@@ -6,2 +6,3 @@ module github.com/stackitcloud/stackit-cli

require (
github.com/fatih/color v1.14.1
github.com/goccy/go-yaml v1.11.3

@@ -19,11 +20,12 @@ github.com/golang-jwt/jwt/v5 v5.2.1

github.com/stackitcloud/stackit-sdk-go/core v0.12.0
github.com/stackitcloud/stackit-sdk-go/services/authorization v0.2.0
github.com/stackitcloud/stackit-sdk-go/services/dns v0.9.1
github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v0.13.0
github.com/stackitcloud/stackit-sdk-go/services/authorization v0.3.0
github.com/stackitcloud/stackit-sdk-go/services/dns v0.10.0
github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v0.14.0
github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.14.0
github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.13.0
github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.14.0
github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.8.0
github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.7.0
github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.8.0
github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.4.0
github.com/stackitcloud/stackit-sdk-go/services/ske v0.15.0
github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v0.2.0
github.com/zalando/go-keyring v0.2.4

@@ -45,3 +47,2 @@ golang.org/x/mod v0.17.0

require (
github.com/fatih/color v1.14.1 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect

@@ -78,3 +79,3 @@ golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect

github.com/spf13/cast v1.6.0 // indirect
github.com/stackitcloud/stackit-sdk-go/services/argus v0.10.0
github.com/stackitcloud/stackit-sdk-go/services/argus v0.11.0
github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v0.12.0

@@ -81,0 +82,0 @@ github.com/stackitcloud/stackit-sdk-go/services/logme v0.14.0

+14
-12

@@ -127,8 +127,8 @@ github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0=

github.com/stackitcloud/stackit-sdk-go/core v0.12.0/go.mod h1:mDX1mSTsB3mP+tNBGcFNx6gH1mGBN4T+dVt+lcw7nlw=
github.com/stackitcloud/stackit-sdk-go/services/argus v0.10.0 h1:FAYOt6UBy/F2jPH2C/NnZnbjLZryJBjtM3afLVgGc4w=
github.com/stackitcloud/stackit-sdk-go/services/argus v0.10.0/go.mod h1:nVllQfYODhX1q3bgwVTLO7wHOp+8NMLiKbn3u/Dg5nU=
github.com/stackitcloud/stackit-sdk-go/services/authorization v0.2.0 h1:vdv8DiG9KN6r0UilBeNbwEWItX/HqUMm6kab3t71kpY=
github.com/stackitcloud/stackit-sdk-go/services/authorization v0.2.0/go.mod h1:1sLuXa7Qvp9f+wKWdRjyNe8B2F8JX7nSTd8fBKadri4=
github.com/stackitcloud/stackit-sdk-go/services/dns v0.9.1 h1:pj2nAJvgzFSckA56rCPdi7StXGrr06go8qejI1weNJ8=
github.com/stackitcloud/stackit-sdk-go/services/dns v0.9.1/go.mod h1:MdZcRbs19s2NLeJmSLSoqTzm9IPIQhE1ZEMpo9gePq0=
github.com/stackitcloud/stackit-sdk-go/services/argus v0.11.0 h1:JVEx/ouHB6PlwGzQa3ywyDym1HTWo3WgrxAyXprCnuM=
github.com/stackitcloud/stackit-sdk-go/services/argus v0.11.0/go.mod h1:nVllQfYODhX1q3bgwVTLO7wHOp+8NMLiKbn3u/Dg5nU=
github.com/stackitcloud/stackit-sdk-go/services/authorization v0.3.0 h1:AyzBgcbd0rCm+2+xaWqtfibjWmkKlO+U+7qxqvtKpJ8=
github.com/stackitcloud/stackit-sdk-go/services/authorization v0.3.0/go.mod h1:1sLuXa7Qvp9f+wKWdRjyNe8B2F8JX7nSTd8fBKadri4=
github.com/stackitcloud/stackit-sdk-go/services/dns v0.10.0 h1:QIZfs6nJ/l2pOweH1E+wazXnlAUtqisVbYUxWAokTbc=
github.com/stackitcloud/stackit-sdk-go/services/dns v0.10.0/go.mod h1:MdZcRbs19s2NLeJmSLSoqTzm9IPIQhE1ZEMpo9gePq0=
github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v0.12.0 h1:LAteZO46XmqTsmPw0QV8n8WiGM205pxrcqHqWznNmyY=

@@ -140,4 +140,4 @@ github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v0.12.0/go.mod h1:wsO3+vXe1XiKLeCIctWAptaHQZ07Un7kmLTQ+drbj7w=

github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.14.0/go.mod h1:kPetkX9hNm9HkRyiKQL/tlgdi8frZdMP8afg0mEvQ9s=
github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v0.13.0 h1:Dhanx9aV5VRfpHg22Li07661FbRT5FR9/M6FowN08a8=
github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v0.13.0/go.mod h1:iFerEzGmkg6R13ldFUyHUWHm0ac9cS4ftTDLhP0k/dU=
github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v0.14.0 h1:FaJYVfha+atvPfFIf3h3+BFjOjeux9OBHukG1J98kq0=
github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v0.14.0/go.mod h1:iFerEzGmkg6R13ldFUyHUWHm0ac9cS4ftTDLhP0k/dU=
github.com/stackitcloud/stackit-sdk-go/services/objectstorage v0.9.0 h1:rWgy4/eCIgyA2dUuc4a30pldmS6taQDwiLqoeZmyeP8=

@@ -147,4 +147,4 @@ github.com/stackitcloud/stackit-sdk-go/services/objectstorage v0.9.0/go.mod h1:dkVMJI88eJ3Xs0ZV15r4tUpgitUGJXcvrX3RL4Zq2bQ=

github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.14.0/go.mod h1:ZecMIf9oYj2DGZqWh93l97WdVaRdLl+tW5Fq3YKGwBM=
github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.13.0 h1:PGLjBZxWM7NIrH1+W1+f+/4kZEgwv9DGnXcUzOqM0M8=
github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.13.0/go.mod h1:SdrqGLCkilL6wl1+jcxmLtks2IocgIg+bsyeyYUIzR4=
github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.14.0 h1:PZAqXd8TVyTZo8qty4bM2sSoLlLG+Nc9tcpxbQhO+GY=
github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.14.0/go.mod h1:SdrqGLCkilL6wl1+jcxmLtks2IocgIg+bsyeyYUIzR4=
github.com/stackitcloud/stackit-sdk-go/services/rabbitmq v0.14.0 h1:wJ+LSMrRol4wlm/ML4wvVPGwIw51VHMFwMCOtwluvKQ=

@@ -156,4 +156,4 @@ github.com/stackitcloud/stackit-sdk-go/services/rabbitmq v0.14.0/go.mod h1:eSgnPBknTJh7t+jVKN+xzeAh+Cg1USOlH3QCyfvG20g=

github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.8.0/go.mod h1:p16qz/pAW8b1gEhqMpIgJfutRPeDPqQLlbVGyCo3f8o=
github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.7.0 h1:1Ho+M4DyZHrwbDe1peW//x+/hegIuaUdZqbQEbPlr4k=
github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.7.0/go.mod h1:LX0Mcyr7/QP77zf7e05fHCJO38RMuTxr7nEDUDZ3oPQ=
github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.8.0 h1:pJBG455kmtbQFpCxcBfBK8wOuEnmsMv3h90LFcdj3q0=
github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.8.0/go.mod h1:LX0Mcyr7/QP77zf7e05fHCJO38RMuTxr7nEDUDZ3oPQ=
github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.4.0 h1:JB1O0E9+L50ZaO36uz7azurvUuB5JdX5s2ZXuIdb9t8=

@@ -163,2 +163,4 @@ github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.4.0/go.mod h1:Ni9RBJvcaXRIrDIuQBpJcuQvCQSj27crQSyc+WM4p0c=

github.com/stackitcloud/stackit-sdk-go/services/ske v0.15.0/go.mod h1:0fFs4R7kg+gU7FNAIzzFvlCZJz6gyZ8CFhbK3eSrAwQ=
github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v0.2.0 h1:aIXxXx6u4+6C02MPb+hdItigeKeen7m+hEEG+Ej9sNs=
github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v0.2.0/go.mod h1:fQJOQMfasStZ8J9iGX0vTjyJoQtLqMXJ5Npb03QJk84=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

@@ -165,0 +167,0 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=

@@ -25,3 +25,6 @@ package generatepayload

const testJobName = "test-job-name"
const (
testJobName = "test-job-name"
testFilePath = "example-file"
)

@@ -33,2 +36,3 @@ func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {

jobNameFlag: testJobName,
filePathFlag: testFilePath,
}

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

JobName: utils.Ptr(testJobName),
FilePath: utils.Ptr(testFilePath),
}

@@ -87,2 +92,12 @@ for _, mod := range mods {

{
description: "file path missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, filePathFlag)
}),
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.FilePath = nil
}),
},
{
description: "job name missing",

@@ -89,0 +104,0 @@ flagValues: fixtureFlagValues(func(flagValues map[string]string) {

@@ -10,2 +10,3 @@ package generatepayload

"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
"github.com/stackitcloud/stackit-cli/internal/pkg/fileutils"
"github.com/stackitcloud/stackit-cli/internal/pkg/flags"

@@ -24,2 +25,3 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"

instanceIdFlag = "instance-id"
filePathFlag = "file-path"
)

@@ -31,2 +33,3 @@

InstanceId string
FilePath *string
}

@@ -50,3 +53,3 @@

`Generate a Create payload with default values, and adapt it with custom values for the different configuration options`,
`$ stackit argus scrape-config generate-payload > ./payload.json`,
`$ stackit argus scrape-config generate-payload --file-path ./payload.json`,
`<Modify payload in file, if needed>`,

@@ -56,5 +59,8 @@ `$ stackit argus scrape-config create my-config --payload @./payload.json`),

`Generate an Update payload with the values of an existing configuration named "my-config" for Argus instance xxx, and adapt it with custom values for the different configuration options`,
`$ stackit argus scrape-config generate-payload --job-name my-config --instance-id xxx > ./payload.json`,
`$ stackit argus scrape-config generate-payload --job-name my-config --instance-id xxx --file-path ./payload.json`,
`<Modify payload in file>`,
`$ stackit argus scrape-config update my-config --payload @./payload.json`),
examples.NewExample(
`Generate an Update payload with the values of an existing configuration named "my-config" for Argus instance xxx, and preview it in the terminal`,
`$ stackit argus scrape-config generate-payload --job-name my-config --instance-id xxx`),
),

@@ -76,3 +82,3 @@ RunE: func(cmd *cobra.Command, args []string) error {

createPayload := argusUtils.DefaultCreateScrapeConfigPayload
return outputCreateResult(p, &createPayload)
return outputCreateResult(p, model.FilePath, &createPayload)
}

@@ -91,3 +97,3 @@

return outputUpdateResult(p, payload)
return outputUpdateResult(p, model.FilePath, payload)
},

@@ -102,2 +108,3 @@ }

cmd.Flags().StringP(jobNameFlag, "n", "", "If set, generates an update payload with the current state of the given scrape config. If unset, generates a create payload with default values")
cmd.Flags().StringP(filePathFlag, "f", "", "If set, writes the payload to the given file. If unset, writes the payload to the standard output")
}

@@ -119,2 +126,3 @@

InstanceId: flags.FlagToStringValue(p, cmd, instanceIdFlag),
FilePath: flags.FlagToStringPointer(p, cmd, filePathFlag),
}, nil

@@ -128,3 +136,3 @@ }

func outputCreateResult(p *print.Printer, payload *argus.CreateScrapeConfigPayload) error {
func outputCreateResult(p *print.Printer, filePath *string, payload *argus.CreateScrapeConfigPayload) error {
payloadBytes, err := json.MarshalIndent(*payload, "", " ")

@@ -134,8 +142,16 @@ if err != nil {

}
p.Outputln(string(payloadBytes))
if filePath != nil {
err = fileutils.WriteToFile(*filePath, string(payloadBytes))
if err != nil {
return fmt.Errorf("write payload to the file: %w", err)
}
} else {
p.Outputln(string(payloadBytes))
}
return nil
}
func outputUpdateResult(p *print.Printer, payload *argus.UpdateScrapeConfigPayload) error {
func outputUpdateResult(p *print.Printer, filePath *string, payload *argus.UpdateScrapeConfigPayload) error {
payloadBytes, err := json.MarshalIndent(*payload, "", " ")

@@ -145,5 +161,13 @@ if err != nil {

}
p.Outputln(string(payloadBytes))
if filePath != nil {
err = fileutils.WriteToFile(*filePath, string(payloadBytes))
if err != nil {
return fmt.Errorf("write payload to the file: %w", err)
}
} else {
p.Outputln(string(payloadBytes))
}
return nil
}

@@ -6,4 +6,6 @@ package activateserviceaccount

"github.com/stackitcloud/stackit-cli/internal/pkg/auth"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/zalando/go-keyring"

@@ -124,1 +126,60 @@ "github.com/google/go-cmp/cmp"

}
func TestStoreFlags(t *testing.T) {
tests := []struct {
description string
model *inputModel
isValid bool
}{
{
description: "base",
model: fixtureInputModel(),
isValid: true,
},
{
description: "no values",
model: &inputModel{
ServiceAccountToken: "",
ServiceAccountKeyPath: "",
PrivateKeyPath: "",
TokenCustomEndpoint: "",
JwksCustomEndpoint: "",
},
isValid: true,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
// Initialize an empty keyring
keyring.MockInit()
err := storeFlags(tt.model)
if !tt.isValid {
if err == nil {
t.Fatalf("did not fail on invalid input")
}
return
}
if err != nil {
t.Fatalf("store flags: %v", err)
}
value, err := auth.GetAuthField(auth.TOKEN_CUSTOM_ENDPOINT)
if err != nil {
t.Errorf("Failed to get value of auth field: %v", err)
}
if value != tt.model.TokenCustomEndpoint {
t.Errorf("Value of \"%s\" does not match: expected \"%s\", got \"%s\"", auth.TOKEN_CUSTOM_ENDPOINT, tt.model.TokenCustomEndpoint, value)
}
value, err = auth.GetAuthField(auth.JWKS_CUSTOM_ENDPOINT)
if err != nil {
t.Errorf("Failed to get value of auth field: %v", err)
}
if value != tt.model.JwksCustomEndpoint {
t.Errorf("Value of \"%s\" does not match: expected \"%s\", got \"%s\"", auth.JWKS_CUSTOM_ENDPOINT, tt.model.TokenCustomEndpoint, value)
}
})
}
}

@@ -16,4 +16,4 @@ package auth

Use: "auth",
Short: "Provides authentication functionality",
Long: "Provides authentication functionality.",
Short: "Authenticates the STACKIT CLI",
Long: "Authenticates in the STACKIT CLI.",
Args: args.NoArgs,

@@ -20,0 +20,0 @@ Run: utils.CmdHelp,

@@ -38,2 +38,3 @@ package set

skeCustomEndpointFlag = "ske-custom-endpoint"
sqlServerFlexCustomEndpointFlag = "sqlserverflex-custom-endpoint"
)

@@ -143,2 +144,3 @@

cmd.Flags().String(skeCustomEndpointFlag, "", "SKE API base URL, used in calls to this API")
cmd.Flags().String(sqlServerFlexCustomEndpointFlag, "", "SQLServer Flex API base URL, used in calls to this API")

@@ -165,2 +167,6 @@ err := viper.BindPFlag(config.ArgusCustomEndpointKey, cmd.Flags().Lookup(argusCustomEndpointFlag))

cobra.CheckErr(err)
err = viper.BindPFlag(config.RabbitMQCustomEndpointKey, cmd.Flags().Lookup(rabbitMQCustomEndpointFlag))
cobra.CheckErr(err)
err = viper.BindPFlag(config.RedisCustomEndpointKey, cmd.Flags().Lookup(redisCustomEndpointFlag))
cobra.CheckErr(err)
err = viper.BindPFlag(config.ResourceManagerEndpointKey, cmd.Flags().Lookup(skeCustomEndpointFlag))

@@ -174,6 +180,4 @@ cobra.CheckErr(err)

cobra.CheckErr(err)
err = viper.BindPFlag(config.RedisCustomEndpointKey, cmd.Flags().Lookup(redisCustomEndpointFlag))
err = viper.BindPFlag(config.SQLServerFlexCustomEndpointKey, cmd.Flags().Lookup(sqlServerFlexCustomEndpointFlag))
cobra.CheckErr(err)
err = viper.BindPFlag(config.RabbitMQCustomEndpointKey, cmd.Flags().Lookup(rabbitMQCustomEndpointFlag))
cobra.CheckErr(err)
}

@@ -180,0 +184,0 @@

@@ -34,2 +34,3 @@ package unset

skeCustomEndpointFlag: true,
sqlServerFlexCustomEndpointFlag: true,
}

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

SKECustomEndpoint: true,
SQLServerFlexCustomEndpoint: true,
}

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

model.SKECustomEndpoint = false
model.SQLServerFlexCustomEndpoint = false
}),

@@ -112,0 +115,0 @@ },

@@ -41,2 +41,3 @@ package unset

skeCustomEndpointFlag = "ske-custom-endpoint"
sqlServerFlexCustomEndpointFlag = "sqlserverflex-custom-endpoint"
)

@@ -67,2 +68,3 @@

SKECustomEndpoint bool
SQLServerFlexCustomEndpoint bool
}

@@ -154,2 +156,5 @@

}
if model.SQLServerFlexCustomEndpoint {
viper.Set(config.SQLServerFlexCustomEndpointKey, "")
}

@@ -190,2 +195,3 @@ err := config.Write()

cmd.Flags().Bool(skeCustomEndpointFlag, false, "SKE API base URL. If unset, uses the default base URL")
cmd.Flags().Bool(sqlServerFlexCustomEndpointFlag, false, "SQLServer Flex API base URL. If unset, uses the default base URL")
}

@@ -217,2 +223,3 @@

SKECustomEndpoint: flags.FlagToBoolValue(p, cmd, skeCustomEndpointFlag),
SQLServerFlexCustomEndpoint: flags.FlagToBoolValue(p, cmd, sqlServerFlexCustomEndpointFlag),
}

@@ -219,0 +226,0 @@

@@ -54,4 +54,4 @@ package list

Use: "list",
Short: "List DNS record sets",
Long: `List DNS record sets. Successfully deleted record sets are not listed by default.`,
Short: "Lists DNS record sets",
Long: `Lists DNS record sets. Successfully deleted record sets are not listed by default.`,
Args: args.NoArgs,

@@ -58,0 +58,0 @@ Example: examples.Build(

@@ -52,4 +52,4 @@ package list

Use: "list",
Short: "List DNS zones",
Long: `List DNS zones. Successfully deleted zones are not listed by default.`,
Short: "Lists DNS zones",
Long: `Lists DNS zones. Successfully deleted zones are not listed by default.`,
Args: args.NoArgs,

@@ -56,0 +56,0 @@ Example: examples.Build(

@@ -26,6 +26,12 @@ package generatepayload

const (
testLoadBalancerName = "example-name"
testFilePath = "example-file"
)
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
projectIdFlag: testProjectId,
loadBalancerNameFlag: "example-name",
loadBalancerNameFlag: testLoadBalancerName,
filePathFlag: testFilePath,
}

@@ -44,3 +50,4 @@ for _, mod := range mods {

},
LoadBalancerName: utils.Ptr("example-name"),
LoadBalancerName: utils.Ptr(testLoadBalancerName),
FilePath: utils.Ptr(testFilePath),
}

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

func fixtureRequest(mods ...func(request *loadbalancer.ApiGetLoadBalancerRequest)) loadbalancer.ApiGetLoadBalancerRequest {
request := testClient.GetLoadBalancer(testCtx, testProjectId, "example-name")
request := testClient.GetLoadBalancer(testCtx, testProjectId, testLoadBalancerName)
for _, mod := range mods {

@@ -94,2 +101,12 @@ mod(&request)

{
description: "file path missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, filePathFlag)
}),
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.FilePath = nil
}),
},
{
description: "project id missing",

@@ -96,0 +113,0 @@ flagValues: fixtureFlagValues(func(flagValues map[string]string) {

@@ -11,2 +11,3 @@ package generatepayload

"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
"github.com/stackitcloud/stackit-cli/internal/pkg/fileutils"
"github.com/stackitcloud/stackit-cli/internal/pkg/flags"

@@ -24,2 +25,3 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"

loadBalancerNameFlag = "lb-name"
filePathFlag = "file-path"
)

@@ -30,2 +32,3 @@

LoadBalancerName *string
FilePath *string
}

@@ -124,3 +127,3 @@

`Generate a payload, and adapt it with custom values for the different configuration options`,
`$ stackit load-balancer generate-payload > ./payload.json`,
`$ stackit load-balancer generate-payload --file-path ./payload.json`,
`<Modify payload in file, if needed>`,

@@ -130,5 +133,8 @@ `$ stackit load-balancer create --payload @./payload.json`),

`Generate a payload with values of an existing load balancer, and adapt it with custom values for the different configuration options`,
`$ stackit load-balancer generate-payload --lb-name xxx > ./payload.json`,
`$ stackit load-balancer generate-payload --lb-name xxx --file-path ./payload.json`,
`<Modify payload in file>`,
`$ stackit load-balancer update xxx --payload @./payload.json`),
examples.NewExample(
`Generate a payload with values of an existing load balancer, and preview it in the terminal`,
`$ stackit load-balancer generate-payload --lb-name xxx`),
),

@@ -150,3 +156,3 @@ RunE: func(cmd *cobra.Command, args []string) error {

createPayload := DefaultCreateLoadBalancerPayload
return outputCreateResult(p, &createPayload)
return outputCreateResult(p, model.FilePath, &createPayload)
}

@@ -171,3 +177,3 @@

}
return outputUpdateResult(p, updatePayload)
return outputUpdateResult(p, model.FilePath, updatePayload)
},

@@ -181,2 +187,3 @@ }

cmd.Flags().StringP(loadBalancerNameFlag, "n", "", "If set, generates the payload with the current values of the given load balancer. If unset, generates the payload with empty values")
cmd.Flags().StringP(filePathFlag, "f", "", "If set, writes the payload to the given file. If unset, writes the payload to the standard output")
}

@@ -196,2 +203,3 @@

LoadBalancerName: loadBalancerName,
FilePath: flags.FlagToStringPointer(p, cmd, filePathFlag),
}

@@ -216,3 +224,3 @@

func outputCreateResult(p *print.Printer, payload *loadbalancer.CreateLoadBalancerPayload) error {
func outputCreateResult(p *print.Printer, filePath *string, payload *loadbalancer.CreateLoadBalancerPayload) error {
payloadBytes, err := json.MarshalIndent(*payload, "", " ")

@@ -222,8 +230,16 @@ if err != nil {

}
p.Outputln(string(payloadBytes))
if filePath != nil {
err = fileutils.WriteToFile(*filePath, string(payloadBytes))
if err != nil {
return fmt.Errorf("write create load balancer payload to the file: %w", err)
}
} else {
p.Outputln(string(payloadBytes))
}
return nil
}
func outputUpdateResult(p *print.Printer, payload *loadbalancer.UpdateLoadBalancerPayload) error {
func outputUpdateResult(p *print.Printer, filePath *string, payload *loadbalancer.UpdateLoadBalancerPayload) error {
payloadBytes, err := json.MarshalIndent(*payload, "", " ")

@@ -233,4 +249,12 @@ if err != nil {

}
p.Outputln(string(payloadBytes))
if filePath != nil {
err = fileutils.WriteToFile(*filePath, string(payloadBytes))
if err != nil {
return fmt.Errorf("write update load balancer payload to the file: %w", err)
}
} else {
p.Outputln(string(payloadBytes))
}
return nil

@@ -237,0 +261,0 @@ }

@@ -42,4 +42,4 @@ package restore

Long: fmt.Sprintf("%s\n%s\n%s",
"Restores a MongoDB Flex instance from a backup of an instance or clones a MongoDB Flex instance from a point-in-time snapshot.",
"The backup is specified by a backup ID and the point-in-time snapshot is specified by a timestamp.",
"Restores a MongoDB Flex instance from a backup of an instance or clones a MongoDB Flex instance from a point-in-time backup.",
"The backup can be specified by a backup ID or a timestamp.",
"You can specify the instance to which the backup will be applied. If not specified, the backup will be applied to the same instance from which it was taken.",

@@ -145,3 +145,3 @@ ),

cmd.Flags().String(backupIdFlag, "", "Backup ID")
cmd.Flags().String(timestampFlag, "", "Timestamp of the snapshot to use as a source for cloning the instance in a date-time with the RFC3339 layout format, e.g. 2024-01-01T00:00:00Z")
cmd.Flags().String(timestampFlag, "", "Timestamp to restore the instance to, in a date-time with the RFC3339 layout format, e.g. 2024-01-01T00:00:00Z")

@@ -148,0 +148,0 @@ err := flags.MarkFlagsRequired(cmd, instanceIdFlag)

@@ -142,3 +142,3 @@ package schedule

table := tables.NewTable()
table.AddRow("BACKUP SCHEDULE", *instance.BackupSchedule)
table.AddRow("BACKUP SCHEDULE (UTC)", *instance.BackupSchedule)
table.AddSeparator()

@@ -145,0 +145,0 @@ table.AddRow("DAILY SNAPSHOT RETENTION (DAYS)", (*instance.Options)["dailySnapshotRetentionDays"])

@@ -24,6 +24,6 @@ package updateschedule

scheduleFlag = "schedule"
snapshotRetentionDaysFlag = "save-snapshot-days"
dailySnapshotRetentionDaysFlag = "save-daily-snapshot-days"
weeklySnapshotRetentionWeeksFlag = "save-weekly-snapshot-weeks"
monthlySnapshotRetentionMonthsFlag = "save-monthly-snapshot-months"
snapshotRetentionDaysFlag = "store-for-days"
dailySnapshotRetentionDaysFlag = "store-daily-backup-days"
weeklySnapshotRetentionWeeksFlag = "store-weekly-backup-weeks"
monthlySnapshotRetentionMonthsFlag = "store-monthly-backups-months"

@@ -66,4 +66,4 @@ // Default values for the backup schedule options

examples.NewExample(
`Update the retention days for snapshots of a MongoDB Flex instance with ID "xxx" to 5 days`,
"$ stackit mongodbflex backup update-schedule --instance-id xxx --save-snapshot-days 5"),
`Update the retention days for backups of a MongoDB Flex instance with ID "xxx" to 5 days`,
"$ stackit mongodbflex backup update-schedule --instance-id xxx --store-for-days 5"),
),

@@ -126,6 +126,6 @@

cmd.Flags().String(scheduleFlag, "", "Backup schedule, in the cron scheduling system format e.g. '0 0 * * *'")
cmd.Flags().Int64(snapshotRetentionDaysFlag, 0, "Number of days to retain snapshots. Should be less than or equal to the value of the daily backup.")
cmd.Flags().Int64(dailySnapshotRetentionDaysFlag, 0, "Number of days to retain daily snapshots. Should be less than or equal to the number of days of the selected weekly or monthly value.")
cmd.Flags().Int64(weeklySnapshotRetentionWeeksFlag, 0, "Number of weeks to retain weekly snapshots. Should be less than or equal to the number of weeks of the selected monthly value.")
cmd.Flags().Int64(monthlySnapshotRetentionMonthsFlag, 0, "Number of months to retain monthly snapshots")
cmd.Flags().Int64(snapshotRetentionDaysFlag, 0, "Number of days to retain backups. Should be less than or equal to the value of the daily backup.")
cmd.Flags().Int64(dailySnapshotRetentionDaysFlag, 0, "Number of days to retain daily backups. Should be less than or equal to the number of days of the selected weekly or monthly value.")
cmd.Flags().Int64(weeklySnapshotRetentionWeeksFlag, 0, "Number of weeks to retain weekly backups. Should be less than or equal to the number of weeks of the selected monthly value.")
cmd.Flags().Int64(monthlySnapshotRetentionMonthsFlag, 0, "Number of months to retain monthly backups")

@@ -132,0 +132,0 @@ err := flags.MarkFlagsRequired(cmd, instanceIdFlag)

@@ -137,3 +137,3 @@ package describe

table.AddSeparator()
table.AddRow("STORAGE SIZE", *instance.Storage.Size)
table.AddRow("STORAGE SIZE (GB)", *instance.Storage.Size)
table.AddSeparator()

@@ -152,5 +152,5 @@ table.AddRow("VERSION", *instance.Version)

table.AddSeparator()
table.AddRow("RAM", *instance.Flavor.Memory)
table.AddRow("RAM (GB)", *instance.Flavor.Memory)
table.AddSeparator()
table.AddRow("BACKUP SCHEDULE", *instance.BackupSchedule)
table.AddRow("BACKUP SCHEDULE (UTC)", *instance.BackupSchedule)
table.AddSeparator()

@@ -157,0 +157,0 @@ err = table.Display(p)

@@ -19,4 +19,4 @@ package objectstorage

Use: "object-storage",
Short: "Provides functionality regarding Object Storage",
Long: "Provides functionality regarding Object Storage.",
Short: "Provides functionality for Object Storage",
Long: "Provides functionality for Object Storage.",
Args: args.NoArgs,

@@ -23,0 +23,0 @@ Run: utils.CmdHelp,

@@ -17,4 +17,4 @@ package member

Use: "member",
Short: "Provides functionality regarding organization members",
Long: "Provides functionality regarding organization members.",
Short: "Manages organization members",
Long: "Manages organization members.",
Args: args.NoArgs,

@@ -21,0 +21,0 @@ Run: utils.CmdHelp,

@@ -18,5 +18,5 @@ package organization

Use: "organization",
Short: "Provides functionality regarding organizations",
Short: "Manages organizations",
Long: fmt.Sprintf("%s\n%s",
"Provides functionality regarding organizations.",
"Manages organizations.",
"An active STACKIT organization is the root element of the resource hierarchy and a prerequisite to use any STACKIT Cloud Resource / Service.",

@@ -23,0 +23,0 @@ ),

@@ -15,4 +15,4 @@ package role

Use: "role",
Short: "Provides functionality regarding organization roles",
Long: "Provides functionality regarding organization roles.",
Short: "Manages organization roles",
Long: "Manages organization roles.",
Args: args.NoArgs,

@@ -19,0 +19,0 @@ Run: utils.CmdHelp,

@@ -139,3 +139,3 @@ package describe

table.AddSeparator()
table.AddRow("STORAGE SIZE", *instance.Storage.Size)
table.AddRow("STORAGE SIZE (GB)", *instance.Storage.Size)
table.AddSeparator()

@@ -154,3 +154,3 @@ table.AddRow("VERSION", *instance.Version)

table.AddSeparator()
table.AddRow("RAM", *instance.Flavor.Memory)
table.AddRow("RAM (GB)", *instance.Flavor.Memory)
table.AddSeparator()

@@ -157,0 +157,0 @@ table.AddRow("BACKUP SCHEDULE (UTC)", *instance.BackupSchedule)

@@ -17,4 +17,4 @@ package member

Use: "member",
Short: "Provides functionality regarding project members",
Long: "Provides functionality regarding project members.",
Short: "Manages project members",
Long: "Manages project members.",
Args: args.NoArgs,

@@ -21,0 +21,0 @@ Run: utils.CmdHelp,

@@ -23,5 +23,5 @@ package project

Use: "project",
Short: "Provides functionality regarding projects",
Short: "Manages projects",
Long: fmt.Sprintf("%s\n%s",
"Provides functionality regarding projects.",
"Provides functionality for projects.",
"A project is a container for resources which is the service that you can purchase from STACKIT.",

@@ -28,0 +28,0 @@ ),

@@ -15,4 +15,4 @@ package role

Use: "role",
Short: "Provides functionality regarding project roles",
Long: "Provides functionality regarding project roles.",
Short: "Manages project roles",
Long: "Manages project roles.",
Args: args.NoArgs,

@@ -19,0 +19,0 @@ Run: utils.CmdHelp,

@@ -11,2 +11,3 @@ package cmd

"github.com/stackitcloud/stackit-cli/internal/cmd/auth"
"github.com/stackitcloud/stackit-cli/internal/cmd/beta"
"github.com/stackitcloud/stackit-cli/internal/cmd/config"

@@ -35,2 +36,3 @@ "github.com/stackitcloud/stackit-cli/internal/cmd/curl"

"github.com/fatih/color"
"github.com/spf13/cobra"

@@ -65,3 +67,3 @@ "github.com/spf13/viper"

if flags.FlagToBoolValue(p, cmd, "version") {
p.Outputf("STACKIT CLI (BETA)\n")
p.Outputf("STACKIT CLI (beta)\n")

@@ -94,5 +96,23 @@ parsedDate, err := time.Parse(time.RFC3339, date)

beautifyUsageTemplate(cmd)
return cmd
}
func beautifyUsageTemplate(cmd *cobra.Command) {
cobra.AddTemplateFunc("WhiteBold", color.New(color.FgHiWhite, color.Bold).SprintFunc())
usageTemplate := cmd.UsageTemplate()
usageTemplate = strings.NewReplacer(
`Usage:`, `{{WhiteBold "USAGE"}}`,
`Examples:`, `{{WhiteBold "EXAMPLES"}}`,
`Aliases:`, `{{WhiteBold "ALIASES"}}`,
`Available Commands:`, `{{WhiteBold "AVAILABLE COMMANDS"}}`,
`Additional Commands:`, `{{WhiteBold "ADDITIONAL COMMANDS"}}`,
`Global Flags:`, `{{WhiteBold "GLOBAL FLAGS"}}`,
`Flags:`, `{{WhiteBold "FLAGS"}}`,
`Additional help topics:`, `{{WhiteBold "ADDITIONAL HELP TOPICS"}}`,
).Replace(usageTemplate)
cmd.SetUsageTemplate(usageTemplate)
}
func configureFlags(cmd *cobra.Command) error {

@@ -111,2 +131,3 @@ cmd.Flags().BoolP("version", "v", false, `Show "stackit" version`)

cmd.AddCommand(auth.NewCmd(p))
cmd.AddCommand(beta.NewCmd(p))
cmd.AddCommand(config.NewCmd(p))

@@ -113,0 +134,0 @@ cmd.AddCommand(curl.NewCmd(p))

@@ -66,3 +66,3 @@ package create

request = request.UpdateACLsPayload(secretsmanager.UpdateACLsPayload{
Cidrs: utils.Ptr([]secretsmanager.AclUpdate{
Cidrs: utils.Ptr([]secretsmanager.UpdateACLPayload{
{Cidr: utils.Ptr("198.51.100.14/24")},

@@ -285,3 +285,3 @@ })})

expectedRequest: fixtureUpdateACLsRequest().UpdateACLsPayload(secretsmanager.UpdateACLsPayload{
Cidrs: utils.Ptr([]secretsmanager.AclUpdate{
Cidrs: utils.Ptr([]secretsmanager.UpdateACLPayload{
{Cidr: utils.Ptr("198.51.100.14/24")},

@@ -288,0 +288,0 @@ {Cidr: utils.Ptr("1.2.3.4/32")},

@@ -149,6 +149,6 @@ package create

cidrs := make([]secretsmanager.AclUpdate, len(*model.Acls))
cidrs := make([]secretsmanager.UpdateACLPayload, len(*model.Acls))
for i, acl := range *model.Acls {
cidrs[i] = secretsmanager.AclUpdate{Cidr: utils.Ptr(acl)}
cidrs[i] = secretsmanager.UpdateACLPayload{Cidr: utils.Ptr(acl)}
}

@@ -155,0 +155,0 @@

@@ -113,6 +113,6 @@ package describe

func outputResult(p *print.Printer, outputFormat string, instance *secretsmanager.Instance, aclList *secretsmanager.AclList) error {
func outputResult(p *print.Printer, outputFormat string, instance *secretsmanager.Instance, aclList *secretsmanager.ListACLsResponse) error {
output := struct {
*secretsmanager.Instance
*secretsmanager.AclList
*secretsmanager.ListACLsResponse
}{instance, aclList}

@@ -119,0 +119,0 @@

@@ -73,3 +73,3 @@ package update

request = request.UpdateACLsPayload(secretsmanager.UpdateACLsPayload{
Cidrs: utils.Ptr([]secretsmanager.AclUpdate{
Cidrs: utils.Ptr([]secretsmanager.UpdateACLPayload{
{Cidr: utils.Ptr(testACL1)},

@@ -279,3 +279,3 @@ })})

expectedRequest: fixtureRequest().UpdateACLsPayload(secretsmanager.UpdateACLsPayload{
Cidrs: utils.Ptr([]secretsmanager.AclUpdate{
Cidrs: utils.Ptr([]secretsmanager.UpdateACLPayload{
{Cidr: utils.Ptr(testACL1)},

@@ -282,0 +282,0 @@ {Cidr: utils.Ptr(testACL2)},

@@ -127,6 +127,6 @@ package update

cidrs := []secretsmanager.AclUpdate{}
cidrs := []secretsmanager.UpdateACLPayload{}
for _, acl := range *model.Acls {
cidrs = append(cidrs, secretsmanager.AclUpdate{Cidr: utils.Ptr(acl)})
cidrs = append(cidrs, secretsmanager.UpdateACLPayload{Cidr: utils.Ptr(acl)})
}

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

@@ -19,4 +19,4 @@ package key

Use: "key",
Short: "Provides functionality regarding service account keys",
Long: "Provides functionality regarding service account keys.",
Short: "Provides functionality for service account keys",
Long: "Provides functionality for service account keys.",
Args: args.NoArgs,

@@ -23,0 +23,0 @@ Run: utils.CmdHelp,

@@ -17,4 +17,4 @@ package token

Use: "token",
Short: "Provides functionality regarding service account tokens",
Long: "Provides functionality regarding service account tokens.",
Short: "Provides functionality for service account tokens",
Long: "Provides functionality for service account tokens.",
Args: args.NoArgs,

@@ -21,0 +21,0 @@ Run: utils.CmdHelp,

@@ -25,6 +25,12 @@ package generatepayload

const (
testClusterName = "example-name"
testFilePath = "example-file"
)
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
projectIdFlag: testProjectId,
clusterNameFlag: "example-name",
clusterNameFlag: testClusterName,
filePathFlag: testFilePath,
}

@@ -43,3 +49,4 @@ for _, mod := range mods {

},
ClusterName: utils.Ptr("example-name"),
ClusterName: utils.Ptr(testClusterName),
FilePath: utils.Ptr(testFilePath),
}

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

func fixtureRequest(mods ...func(request *ske.ApiGetClusterRequest)) ske.ApiGetClusterRequest {
request := testClient.GetCluster(testCtx, testProjectId, "example-name")
request := testClient.GetCluster(testCtx, testProjectId, testClusterName)
for _, mod := range mods {

@@ -93,2 +100,12 @@ mod(&request)

{
description: "file path missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, filePathFlag)
}),
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.FilePath = nil
}),
},
{
description: "project id missing",

@@ -95,0 +112,0 @@ flagValues: fixtureFlagValues(func(flagValues map[string]string) {

@@ -11,2 +11,3 @@ package generatepayload

"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
"github.com/stackitcloud/stackit-cli/internal/pkg/fileutils"
"github.com/stackitcloud/stackit-cli/internal/pkg/flags"

@@ -24,2 +25,3 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"

clusterNameFlag = "cluster-name"
filePathFlag = "file-path"
)

@@ -30,2 +32,3 @@

ClusterName *string
FilePath *string
}

@@ -45,3 +48,3 @@

`Generate a payload with default values, and adapt it with custom values for the different configuration options`,
`$ stackit ske cluster generate-payload > ./payload.json`,
`$ stackit ske cluster generate-payload --file-path ./payload.json`,
`<Modify payload in file, if needed>`,

@@ -51,5 +54,8 @@ `$ stackit ske cluster create my-cluster --payload @./payload.json`),

`Generate a payload with values of a cluster, and adapt it with custom values for the different configuration options`,
`$ stackit ske cluster generate-payload --cluster-name my-cluster > ./payload.json`,
`$ stackit ske cluster generate-payload --cluster-name my-cluster --file-path ./payload.json`,
`<Modify payload in file>`,
`$ stackit ske cluster update my-cluster --payload @./payload.json`),
examples.NewExample(
`Generate a payload with values of a cluster, and preview it in the terminal`,
`$ stackit ske cluster generate-payload --cluster-name my-cluster`),
),

@@ -91,3 +97,3 @@ RunE: func(cmd *cobra.Command, args []string) error {

return outputResult(p, payload)
return outputResult(p, model.FilePath, payload)
},

@@ -101,2 +107,3 @@ }

cmd.Flags().StringP(clusterNameFlag, "n", "", "If set, generates the payload with the current state of the given cluster. If unset, generates the payload with default values")
cmd.Flags().StringP(filePathFlag, "f", "", "If set, writes the payload to the given file. If unset, writes the payload to the standard output")
}

@@ -116,2 +123,3 @@

ClusterName: clusterName,
FilePath: flags.FlagToStringPointer(p, cmd, filePathFlag),
}

@@ -136,3 +144,3 @@

func outputResult(p *print.Printer, payload *ske.CreateOrUpdateClusterPayload) error {
func outputResult(p *print.Printer, filePath *string, payload *ske.CreateOrUpdateClusterPayload) error {
payloadBytes, err := json.MarshalIndent(*payload, "", " ")

@@ -142,5 +150,13 @@ if err != nil {

}
p.Outputln(string(payloadBytes))
if filePath != nil {
err = fileutils.WriteToFile(*filePath, string(payloadBytes))
if err != nil {
return fmt.Errorf("write payload to the file: %w", err)
}
} else {
p.Outputln(string(payloadBytes))
}
return nil
}

@@ -138,2 +138,23 @@ package options

func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error {
// filter output based on the flags
if !model.AvailabilityZones {
options.AvailabilityZones = nil
}
if !model.KubernetesVersions {
options.KubernetesVersions = nil
}
if !model.MachineImages {
options.MachineImages = nil
}
if !model.MachineTypes {
options.MachineTypes = nil
}
if !model.VolumeTypes {
options.VolumeTypes = nil
}
switch model.OutputFormat {

@@ -156,29 +177,21 @@ case print.JSONOutputFormat:

default:
return outputResultAsTable(p, model, options)
return outputResultAsTable(p, options)
}
}
func outputResultAsTable(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error {
func outputResultAsTable(p *print.Printer, options *ske.ProviderOptions) error {
content := ""
if model.AvailabilityZones {
content += renderAvailabilityZones(options)
content += renderAvailabilityZones(options)
kubernetesVersionsRendered, err := renderKubernetesVersions(options)
if err != nil {
return fmt.Errorf("render Kubernetes versions: %w", err)
}
if model.KubernetesVersions {
kubernetesVersionsRendered, err := renderKubernetesVersions(options)
if err != nil {
return fmt.Errorf("render Kubernetes versions: %w", err)
}
content += kubernetesVersionsRendered
}
if model.MachineImages {
content += renderMachineImages(options)
}
if model.MachineTypes {
content += renderMachineTypes(options)
}
if model.VolumeTypes {
content += renderVolumeTypes(options)
}
content += kubernetesVersionsRendered
err := p.PagerDisplay(content)
content += renderMachineImages(options)
content += renderMachineTypes(options)
content += renderVolumeTypes(options)
err = p.PagerDisplay(content)
if err != nil {

@@ -192,2 +205,6 @@ return fmt.Errorf("display output: %w", err)

func renderAvailabilityZones(resp *ske.ProviderOptions) string {
if resp.AvailabilityZones == nil {
return ""
}
zones := *resp.AvailabilityZones

@@ -206,2 +223,6 @@

func renderKubernetesVersions(resp *ske.ProviderOptions) (string, error) {
if resp.KubernetesVersions == nil {
return "", nil
}
versions := *resp.KubernetesVersions

@@ -228,2 +249,6 @@

func renderMachineImages(resp *ske.ProviderOptions) string {
if resp.MachineImages == nil {
return ""
}
images := *resp.MachineImages

@@ -258,2 +283,6 @@

func renderMachineTypes(resp *ske.ProviderOptions) string {
if resp.MachineTypes == nil {
return ""
}
types := *resp.MachineTypes

@@ -272,2 +301,6 @@

func renderVolumeTypes(resp *ske.ProviderOptions) string {
if resp.VolumeTypes == nil {
return ""
}
types := *resp.VolumeTypes

@@ -274,0 +307,0 @@

@@ -36,2 +36,3 @@ package config

SKECustomEndpointKey = "ske_custom_endpoint"
SQLServerFlexCustomEndpointKey = "sqlserverflex_custom_endpoint"

@@ -74,2 +75,3 @@ AsyncDefault = false

SKECustomEndpointKey,
SQLServerFlexCustomEndpointKey,
}

@@ -151,2 +153,3 @@

viper.SetDefault(SKECustomEndpointKey, "")
viper.SetDefault(SQLServerFlexCustomEndpointKey, "")
}

@@ -197,6 +197,5 @@ package errors

type DatabaseInputFlavorError struct {
Service string
Operation string
Cmd *cobra.Command
Args []string
Service string
Cmd *cobra.Command
Args []string
}

@@ -209,6 +208,9 @@

}
// Assumes a structure of the form "stackit <service> <resource> <operation>"
service := e.Cmd.Parent().Parent().Use
return fmt.Sprintf(DATABASE_INVALID_INPUT_FLAVOR, fullCommandPath, service)
if e.Service == "" {
// Assumes a structure of the form "stackit <service> <resource> <operation>"
e.Service = e.Cmd.Parent().Parent().Use
}
return fmt.Sprintf(DATABASE_INVALID_INPUT_FLAVOR, fullCommandPath, e.Service)
}

@@ -215,0 +217,0 @@

@@ -16,3 +16,3 @@ package print

var defaultHTTPHeaders = []string{"Accept", "Content-Type", "Content-Length", "User-Agent", "Date", "Referrer-Policy"}
var defaultHTTPHeaders = []string{"Accept", "Content-Type", "Content-Length", "User-Agent", "Date", "Referrer-Policy", "Traceparent"}

@@ -19,0 +19,0 @@ // BuildDebugStrFromInputModel converts an input model to a user-friendly string representation.

@@ -647,1 +647,53 @@ package utils

}
func TestGetInstanceType(t *testing.T) {
tests := []struct {
description string
numReplicas int64
expectedOutput string
isValid bool
}{
{
description: "single",
numReplicas: 1,
expectedOutput: "Single",
isValid: true,
},
{
description: "replica set",
numReplicas: 3,
expectedOutput: "Replica",
isValid: true,
},
{
description: "sharded cluster",
numReplicas: 9,
expectedOutput: "Sharded",
isValid: true,
},
{
description: "invalid",
numReplicas: 0,
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
output, err := GetInstanceType(tt.numReplicas)
if !tt.isValid {
if err == nil {
t.Fatalf("did not fail on invalid input")
}
return
}
if err != nil {
t.Fatalf("failed on valid input: %v", err)
}
if output != tt.expectedOutput {
t.Fatalf("expected output to be %s, got %s", tt.expectedOutput, output)
}
})
}
}

@@ -572,1 +572,47 @@ package utils

}
func TestGetInstanceType(t *testing.T) {
tests := []struct {
description string
numReplicas int64
expectedOutput string
isValid bool
}{
{
description: "single",
numReplicas: 1,
expectedOutput: "Single",
isValid: true,
},
{
description: "replica set",
numReplicas: 3,
expectedOutput: "Replica",
isValid: true,
},
{
description: "invalid",
numReplicas: 0,
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
output, err := GetInstanceType(tt.numReplicas)
if !tt.isValid {
if err == nil {
t.Fatalf("did not fail on invalid input")
}
return
}
if err != nil {
t.Fatalf("failed on valid input: %v", err)
}
if output != tt.expectedOutput {
t.Fatalf("expected output to be %s, got %s", tt.expectedOutput, output)
}
})
}
}

@@ -613,1 +613,28 @@ package utils

}
func TestGetDefaultKubeconfigPath(t *testing.T) {
tests := []struct {
description string
}{
{
description: "base",
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
output, err := GetDefaultKubeconfigPath()
if err != nil {
t.Errorf("failed on valid input")
}
userHome, err := os.UserHomeDir()
if err != nil {
t.Errorf("could not get user home directory")
}
if output != filepath.Join(userHome, ".kube", "config") {
t.Errorf("expected output to be %s, got %s", filepath.Join(userHome, ".kube", "config"), output)
}
})
}
}
+23
-25

@@ -52,22 +52,22 @@ [![Go Report Card](https://goreportcard.com/badge/github.com/stackitcloud/stackit-cli)](https://goreportcard.com/report/github.com/stackitcloud/stackit-cli) ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/stackitcloud/stackit-cli) [![GitHub License](https://img.shields.io/github/license/stackitcloud/stackit-cli)](https://www.apache.org/licenses/LICENSE-2.0)

| Service | CLI Commands | Status |
| ---------------------------------- | ------------------------- | ----------------------- |
| Argus | `argus` | :white_check_mark: |
| Infrastructure as a Service (IaaS) | | Will be integrated soon |
| Authorization | `project`, `organization` | :white_check_mark: |
| DNS | `dns` | :white_check_mark: |
| Kubernetes Engine (SKE) | `ske` | :white_check_mark: |
| Load Balancer | `load-balancer` | :white_check_mark: |
| LogMe | `logme` | :white_check_mark: |
| MariaDB | `mariadb` | :white_check_mark: |
| MongoDB Flex | `mongodbflex` | :white_check_mark: |
| Object Storage | `object-storage` | :white_check_mark: |
| OpenSearch | `opensearch` | :white_check_mark: |
| PostgreSQL Flex | `postgresflex` | :white_check_mark: |
| RabbitMQ | `rabbitmq` | :white_check_mark: |
| Redis | `redis` | :white_check_mark: |
| Resource Manager | `project` | :white_check_mark: |
| Secrets Manager | `secrets-manager` | :white_check_mark: |
| Service Account | `service-account` | :white_check_mark: |
| SQLServer Flex | | Will be integrated soon |
| Service | CLI Commands | Status |
| ---------------------------------- | ------------------------- | ------------------------- |
| Argus | `argus` | :white_check_mark: |
| Infrastructure as a Service (IaaS) | | Will be integrated soon |
| Authorization | `project`, `organization` | :white_check_mark: |
| DNS | `dns` | :white_check_mark: |
| Kubernetes Engine (SKE) | `ske` | :white_check_mark: |
| Load Balancer | `load-balancer` | :white_check_mark: |
| LogMe | `logme` | :white_check_mark: |
| MariaDB | `mariadb` | :white_check_mark: |
| MongoDB Flex | `mongodbflex` | :white_check_mark: |
| Object Storage | `object-storage` | :white_check_mark: |
| OpenSearch | `opensearch` | :white_check_mark: |
| PostgreSQL Flex | `postgresflex` | :white_check_mark: |
| RabbitMQ | `rabbitmq` | :white_check_mark: |
| Redis | `redis` | :white_check_mark: |
| Resource Manager | `project` | :white_check_mark: |
| Secrets Manager | `secrets-manager` | :white_check_mark: |
| Service Account | `service-account` | :white_check_mark: |
| SQLServer Flex | `beta sqlserverflex` | :white_check_mark: (beta) |

@@ -143,2 +143,3 @@ ## Authentication

When using `less` as a pager, STACKIT CLI will automatically pass following options
- -F, --quit-if-one-screen - Less will automatically exit if the entire file can be displayed on the first screen.

@@ -150,14 +151,11 @@ - -S, --chop-long-lines - Lines longer than the screen width will be chopped rather than being folded.

> These options will not be added automatically if a custom pager is defined.
>
>
> In that case, users can define the parameters by using the specific environment variable required by the `PAGER` (if supported).
>
> For example, if user sets the `PAGER` environment variable to `less` and would like to pass some arguments, `LESS` environment variable must be used as following:
>
> export PAGER="less"
>
>
> export LESS="-R"
## Autocompletion

@@ -164,0 +162,0 @@