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

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

Package Overview
Dependencies
Versions
173
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

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

Comparing version
v0.54.1
to
v0.55.0
+43
docs/stackit_organization_describe.md
## stackit organization describe
Show an organization
### Synopsis
Show an organization.
```
stackit organization describe [flags]
```
### Examples
```
Describe the organization with the organization uuid "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$ stackit organization describe xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Describe the organization with the container id "foo-bar-organization"
$ stackit organization describe foo-bar-organization
```
### Options
```
-h, --help Help for "stackit organization describe"
```
### Options inherited from parent commands
```
-y, --assume-yes If set, skips all confirmation prompts
--async If set, runs the command asynchronously
-o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"]
-p, --project-id string Project ID
--region string Target region for region-specific requests
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```
### SEE ALSO
* [stackit organization](./stackit_organization.md) - Manages organizations
## stackit organization list
Lists all organizations
### Synopsis
Lists all organizations.
```
stackit organization list [flags]
```
### Examples
```
Lists organizations for your user
$ stackit organization list
Lists the first 10 organizations
$ stackit organization list --limit 10
```
### Options
```
-h, --help Help for "stackit organization list"
--limit int Maximum number of entries to list (default 50)
```
### Options inherited from parent commands
```
-y, --assume-yes If set, skips all confirmation prompts
--async If set, runs the command asynchronously
-o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"]
-p, --project-id string Project ID
--region string Target region for region-specific requests
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```
### SEE ALSO
* [stackit organization](./stackit_organization.md) - Manages organizations
## stackit ske options availability-zones
Lists SKE provider options for availability-zones
### Synopsis
Lists STACKIT Kubernetes Engine (SKE) provider options for availability-zones.
```
stackit ske options availability-zones [flags]
```
### Examples
```
List SKE options for availability-zones
$ stackit ske options availability-zones
```
### Options
```
-h, --help Help for "stackit ske options availability-zones"
```
### Options inherited from parent commands
```
-y, --assume-yes If set, skips all confirmation prompts
--async If set, runs the command asynchronously
-o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"]
-p, --project-id string Project ID
--region string Target region for region-specific requests
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```
### SEE ALSO
* [stackit ske options](./stackit_ske_options.md) - Lists SKE provider options
## stackit ske options kubernetes-versions
Lists SKE provider options for kubernetes-versions
### Synopsis
Lists STACKIT Kubernetes Engine (SKE) provider options for kubernetes-versions.
```
stackit ske options kubernetes-versions [flags]
```
### Examples
```
List SKE options for kubernetes-versions
$ stackit ske options kubernetes-versions
List SKE options for supported kubernetes-versions
$ stackit ske options kubernetes-versions --supported
```
### Options
```
-h, --help Help for "stackit ske options kubernetes-versions"
--supported List supported versions only
```
### Options inherited from parent commands
```
-y, --assume-yes If set, skips all confirmation prompts
--async If set, runs the command asynchronously
-o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"]
-p, --project-id string Project ID
--region string Target region for region-specific requests
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```
### SEE ALSO
* [stackit ske options](./stackit_ske_options.md) - Lists SKE provider options
## stackit ske options machine-images
Lists SKE provider options for machine-images
### Synopsis
Lists STACKIT Kubernetes Engine (SKE) provider options for machine-images.
```
stackit ske options machine-images [flags]
```
### Examples
```
List SKE options for machine-images
$ stackit ske options machine-images
```
### Options
```
-h, --help Help for "stackit ske options machine-images"
```
### Options inherited from parent commands
```
-y, --assume-yes If set, skips all confirmation prompts
--async If set, runs the command asynchronously
-o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"]
-p, --project-id string Project ID
--region string Target region for region-specific requests
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```
### SEE ALSO
* [stackit ske options](./stackit_ske_options.md) - Lists SKE provider options
## stackit ske options machine-types
Lists SKE provider options for machine-types
### Synopsis
Lists STACKIT Kubernetes Engine (SKE) provider options for machine-types.
```
stackit ske options machine-types [flags]
```
### Examples
```
List SKE options for machine-types
$ stackit ske options machine-types
```
### Options
```
-h, --help Help for "stackit ske options machine-types"
```
### Options inherited from parent commands
```
-y, --assume-yes If set, skips all confirmation prompts
--async If set, runs the command asynchronously
-o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"]
-p, --project-id string Project ID
--region string Target region for region-specific requests
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```
### SEE ALSO
* [stackit ske options](./stackit_ske_options.md) - Lists SKE provider options
## stackit ske options volume-types
Lists SKE provider options for volume-types
### Synopsis
Lists STACKIT Kubernetes Engine (SKE) provider options for volume-types.
```
stackit ske options volume-types [flags]
```
### Examples
```
List SKE options for volume-types
$ stackit ske options volume-types
```
### Options
```
-h, --help Help for "stackit ske options volume-types"
```
### Options inherited from parent commands
```
-y, --assume-yes If set, skips all confirmation prompts
--async If set, runs the command asynchronously
-o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"]
-p, --project-id string Project ID
--region string Target region for region-specific requests
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```
### SEE ALSO
* [stackit ske options](./stackit_ske_options.md) - Lists SKE provider options
package describe
import (
"context"
"testing"
"time"
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/testutils"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/stackitcloud/stackit-sdk-go/services/resourcemanager"
)
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &resourcemanager.APIClient{}
var (
testOrganizationId = uuid.NewString()
)
func fixtureArgValues(mods ...func(argValues []string)) []string {
argValues := []string{
testOrganizationId,
}
for _, mod := range mods {
mod(argValues)
}
return argValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
Verbosity: globalflags.VerbosityDefault,
},
OrganizationId: testOrganizationId,
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureRequest(mods ...func(request *resourcemanager.ApiGetOrganizationRequest)) resourcemanager.ApiGetOrganizationRequest {
request := testClient.GetOrganization(testCtx, testOrganizationId)
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(),
isValid: true,
expectedModel: fixtureInputModel(),
},
{
description: "uuid as example for an organization id",
argValues: []string{"12345678-90ab-cdef-1234-1234567890ab"},
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.OrganizationId = "12345678-90ab-cdef-1234-1234567890ab"
}),
},
{
description: "non uuid string as example for a container id",
argValues: []string{"foo-bar-organization"},
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.OrganizationId = "foo-bar-organization"
}),
},
{
description: "no args",
argValues: []string{},
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid)
})
}
}
func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest resourcemanager.ApiGetOrganizationRequest
}{
{
description: "base",
model: fixtureInputModel(),
expectedRequest: fixtureRequest(),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
request := buildRequest(testCtx, tt.model, testClient)
diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmpopts.EquateComparable(testCtx),
)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
func TestOutputResult(t *testing.T) {
type args struct {
outputFormat string
organization *resourcemanager.OrganizationResponse
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "empty",
args: args{},
wantErr: false,
},
{
name: "nil pointer as organization",
args: args{
organization: nil,
},
wantErr: false,
},
{
name: "empty organization",
args: args{
organization: utils.Ptr(resourcemanager.OrganizationResponse{}),
},
wantErr: false,
},
{
name: "full response",
args: args{
organization: utils.Ptr(resourcemanager.OrganizationResponse{
OrganizationId: utils.Ptr(uuid.NewString()),
Name: utils.Ptr("foo bar"),
LifecycleState: utils.Ptr(resourcemanager.LIFECYCLESTATE_ACTIVE),
ContainerId: utils.Ptr("foo-bar-organization"),
CreationTime: utils.Ptr(time.Now()),
UpdateTime: utils.Ptr(time.Now()),
Labels: utils.Ptr(map[string]string{
"foo": "true",
"bar": "false",
}),
}),
},
wantErr: false,
},
}
p := print.NewPrinter()
p.Cmd = NewCmd(&types.CmdParams{Printer: p})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := outputResult(p, tt.args.outputFormat, tt.args.organization); (err != nil) != tt.wantErr {
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
package describe
import (
"context"
"fmt"
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"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/resourcemanager/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/resourcemanager"
)
const (
organizationIdArg = "ORGANIZATION_ID"
)
type inputModel struct {
*globalflags.GlobalFlagModel
OrganizationId string
}
func NewCmd(params *types.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: "describe",
Short: "Show an organization",
Long: "Show an organization.",
// the arg can be the organization uuid or the container id, which is not a uuid, so no validation needed
Args: args.SingleArg(organizationIdArg, nil),
Example: examples.Build(
examples.NewExample(
`Describe the organization with the organization uuid "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"`,
"$ stackit organization describe xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
),
examples.NewExample(
`Describe the organization with the container id "foo-bar-organization"`,
"$ stackit organization describe foo-bar-organization",
),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(params.Printer, cmd, args)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion)
if err != nil {
return err
}
// Call API
req := buildRequest(ctx, model, apiClient)
resp, err := req.Execute()
if err != nil {
return err
}
return outputResult(params.Printer, model.OutputFormat, resp)
},
}
return cmd
}
func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) {
organizationId := inputArgs[0]
globalFlags := globalflags.Parse(p, cmd)
model := inputModel{
GlobalFlagModel: globalFlags,
OrganizationId: organizationId,
}
p.DebugInputModel(model)
return &model, nil
}
func buildRequest(ctx context.Context, model *inputModel, apiClient *resourcemanager.APIClient) resourcemanager.ApiGetOrganizationRequest {
req := apiClient.GetOrganization(ctx, model.OrganizationId)
return req
}
func outputResult(p *print.Printer, outputFormat string, organization *resourcemanager.OrganizationResponse) error {
return p.OutputResult(outputFormat, organization, func() error {
if organization == nil {
p.Outputln("show organization: empty response")
return nil
}
table := tables.NewTable()
table.AddRow("ORGANIZATION ID", utils.PtrString(organization.OrganizationId))
table.AddSeparator()
table.AddRow("NAME", utils.PtrString(organization.Name))
table.AddSeparator()
table.AddRow("CONTAINER ID", utils.PtrString(organization.ContainerId))
table.AddSeparator()
table.AddRow("STATUS", utils.PtrString(organization.LifecycleState))
table.AddSeparator()
table.AddRow("CREATION TIME", utils.PtrString(organization.CreationTime))
table.AddSeparator()
table.AddRow("UPDATE TIME", utils.PtrString(organization.UpdateTime))
table.AddSeparator()
table.AddRow("LABELS", utils.JoinStringMap(utils.PtrValue(organization.Labels), ": ", ", "))
err := table.Display(p)
if err != nil {
return fmt.Errorf("render table: %w", err)
}
return nil
})
}
package list
import (
"context"
"strconv"
"testing"
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/testutils"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stackitcloud/stackit-sdk-go/services/resourcemanager"
)
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &resourcemanager.APIClient{}
const (
testEmail = "foo@bar"
testLimit = 10
)
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
limitFlag: strconv.Itoa(int(testLimit)),
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
Verbosity: globalflags.VerbosityDefault,
},
Limit: utils.Ptr(int64(testLimit)),
Member: testEmail,
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureRequest(mods ...func(request *resourcemanager.ApiListOrganizationsRequest)) resourcemanager.ApiListOrganizationsRequest {
request := testClient.ListOrganizations(testCtx)
request = request.Limit(testLimit)
request = request.Member(testEmail)
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",
flagValues: fixtureFlagValues(),
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
// model.Member is set by the Run function afterwards
model.Member = ""
}),
},
{
description: "no limit",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, limitFlag)
}),
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
// model.Member is set by the Run function afterwards
model.Member = ""
model.Limit = nil
}),
},
{
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) {
testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid)
})
}
}
func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest resourcemanager.ApiListOrganizationsRequest
}{
{
description: "base",
model: fixtureInputModel(),
expectedRequest: fixtureRequest(),
},
{
description: "empty input model",
model: fixtureInputModel(func(model *inputModel) {
model.Member = ""
model.Limit = nil
}),
expectedRequest: testClient.ListOrganizations(testCtx).Member(""),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
request := buildRequest(testCtx, tt.model, testClient)
diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmpopts.EquateComparable(testCtx),
)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
func TestOutputResult(t *testing.T) {
type args struct {
outputFormat string
organizations []resourcemanager.ListOrganizationsResponseItemsInner
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "empty",
args: args{},
wantErr: false,
},
{
name: "empty organizations slice",
args: args{
organizations: []resourcemanager.ListOrganizationsResponseItemsInner{},
},
wantErr: false,
},
{
name: "empty organization in organizations slice",
args: args{
organizations: []resourcemanager.ListOrganizationsResponseItemsInner{{}},
},
wantErr: false,
},
}
p := print.NewPrinter()
p.Cmd = NewCmd(&types.CmdParams{Printer: p})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := outputResult(p, tt.args.outputFormat, tt.args.organizations); (err != nil) != tt.wantErr {
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
package list
import (
"context"
"fmt"
"github.com/stackitcloud/stackit-cli/internal/pkg/auth"
"github.com/stackitcloud/stackit-cli/internal/pkg/config"
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/errors"
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
"github.com/stackitcloud/stackit-cli/internal/pkg/flags"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/services/resourcemanager/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/resourcemanager"
)
const (
limitFlag = "limit"
)
type inputModel struct {
*globalflags.GlobalFlagModel
Limit *int64
Member string
}
func NewCmd(params *types.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: "Lists all organizations",
Long: "Lists all organizations.",
Args: args.NoArgs,
Example: examples.Build(
examples.NewExample(
`Lists organizations for your user`,
"$ stackit organization list",
),
examples.NewExample(
`Lists the first 10 organizations`,
"$ stackit organization list --limit 10",
),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(params.Printer, cmd, args)
if err != nil {
return err
}
activeProfile, err := config.GetProfile()
if err != nil {
return fmt.Errorf("get profile: %w", err)
}
model.Member = auth.GetProfileEmail(activeProfile)
// Configure API client
apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion)
if err != nil {
return err
}
// Call API
req := buildRequest(ctx, model, apiClient)
resp, err := req.Execute()
if err != nil {
return err
}
if resp == nil {
return fmt.Errorf("list organizations: empty response")
}
return outputResult(params.Printer, model.OutputFormat, utils.PtrValue(resp.Items))
},
}
configureFlags(cmd)
return cmd
}
func configureFlags(cmd *cobra.Command) {
cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list (default 50)")
}
func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)
limit := flags.FlagToInt64Pointer(p, cmd, limitFlag)
if limit != nil && (*limit < 1 || *limit > 100) {
return nil, &errors.FlagValidationError{
Flag: limitFlag,
Details: "must be between 0 and 100",
}
}
model := inputModel{
GlobalFlagModel: globalFlags,
Limit: limit,
}
p.DebugInputModel(model)
return &model, nil
}
func buildRequest(ctx context.Context, model *inputModel, apiClient *resourcemanager.APIClient) resourcemanager.ApiListOrganizationsRequest {
req := apiClient.ListOrganizations(ctx)
req = req.Member(model.Member)
if model.Limit != nil {
req = req.Limit(float32(*model.Limit))
}
return req
}
func outputResult(p *print.Printer, outputFormat string, organizations []resourcemanager.ListOrganizationsResponseItemsInner) error {
return p.OutputResult(outputFormat, organizations, func() error {
if len(organizations) == 0 {
p.Outputln("No organizations found")
return nil
}
table := tables.NewTable()
table.SetHeader("ID", "NAME", "CONTAINER ID")
for _, organization := range organizations {
table.AddRow(
utils.PtrString(organization.OrganizationId),
utils.PtrString(organization.Name),
utils.PtrString(organization.ContainerId),
)
table.AddSeparator()
}
err := table.Display(p)
if err != nil {
return fmt.Errorf("render table: %w", err)
}
return nil
})
}
package availability_zones
import (
"context"
"testing"
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/testutils"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stackitcloud/stackit-sdk-go/services/ske"
)
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &ske.APIClient{}
const testRegion = "eu01"
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
globalflags.RegionFlag: testRegion,
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{
Region: testRegion,
Verbosity: globalflags.VerbosityDefault,
},
}
for _, mod := range mods {
mod(model)
}
return model
}
func TestParseInput(t *testing.T) {
tests := []struct {
description string
argValues []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: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.Region = ""
}),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid)
})
}
}
func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
inputModel *inputModel
expectedRequest ske.ApiListProviderOptionsRequest
}{
{
description: "base",
inputModel: fixtureInputModel(),
expectedRequest: testClient.ListProviderOptions(testCtx, testRegion),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
request := buildRequest(testCtx, testClient, tt.inputModel)
diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmpopts.EquateComparable(testCtx),
)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
func TestOutputResult(t *testing.T) {
type args struct {
model *inputModel
options *ske.ProviderOptions
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "empty",
args: args{},
wantErr: true,
},
{
name: "missing options",
args: args{
model: &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{},
},
},
wantErr: true,
},
{
name: "empty input model",
args: args{
model: &inputModel{},
options: &ske.ProviderOptions{},
},
wantErr: false,
},
{
name: "set model and options",
args: args{
model: &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{},
},
options: &ske.ProviderOptions{},
},
wantErr: false,
},
{
name: "empty values",
args: args{
model: &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{},
},
options: &ske.ProviderOptions{
AvailabilityZones: &[]ske.AvailabilityZone{},
},
},
wantErr: false,
},
{
name: "empty value in values",
args: args{
model: &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{},
},
options: &ske.ProviderOptions{
AvailabilityZones: &[]ske.AvailabilityZone{{}},
},
},
wantErr: false,
},
{
name: "valid values",
args: args{
model: &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{},
},
options: &ske.ProviderOptions{
AvailabilityZones: &[]ske.AvailabilityZone{
{
Name: utils.Ptr("zone1"),
},
{
Name: utils.Ptr("zone2"),
},
},
},
},
wantErr: false,
},
}
p := print.NewPrinter()
p.Cmd = NewCmd(&types.CmdParams{Printer: p})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := outputResult(p, tt.args.model, tt.args.options); (err != nil) != tt.wantErr {
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
package availability_zones
import (
"context"
"fmt"
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
"github.com/stackitcloud/stackit-sdk-go/services/ske"
)
type inputModel struct {
globalflags.GlobalFlagModel
}
func NewCmd(params *types.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: "availability-zones",
Short: "Lists SKE provider options for availability-zones",
Long: "Lists STACKIT Kubernetes Engine (SKE) provider options for availability-zones.",
Args: args.NoArgs,
Example: examples.Build(
examples.NewExample(
`List SKE options for availability-zones`,
"$ stackit ske options availability-zones"),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(params.Printer, cmd, args)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion)
if err != nil {
return err
}
// Call API
req := buildRequest(ctx, apiClient, model)
resp, err := req.Execute()
if err != nil {
return fmt.Errorf("get SKE provider options: %w", err)
}
return outputResult(params.Printer, model, resp)
},
}
return cmd
}
func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)
model := inputModel{
GlobalFlagModel: utils.PtrValue(globalFlags),
}
p.DebugInputModel(model)
return &model, nil
}
func buildRequest(ctx context.Context, apiClient *ske.APIClient, model *inputModel) ske.ApiListProviderOptionsRequest {
req := apiClient.ListProviderOptions(ctx, model.Region)
return req
}
func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error {
if options == nil {
return fmt.Errorf("options is nil")
}
options.KubernetesVersions = nil
options.MachineImages = nil
options.MachineTypes = nil
options.VolumeTypes = nil
return p.OutputResult(model.OutputFormat, options, func() error {
zones := utils.PtrValue(options.AvailabilityZones)
table := tables.NewTable()
table.SetHeader("ZONE")
for i := range zones {
z := zones[i]
table.AddRow(utils.PtrValue(z.Name))
}
err := table.Display(p)
if err != nil {
return fmt.Errorf("display output: %w", err)
}
return nil
})
}
package kubernetes_versions
import (
"context"
"testing"
"time"
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/testutils"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stackitcloud/stackit-sdk-go/services/ske"
)
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &ske.APIClient{}
const testRegion = "eu01"
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
globalflags.RegionFlag: testRegion,
supportedFlag: "false",
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{
Region: testRegion,
Verbosity: globalflags.VerbosityDefault,
},
Supported: false,
}
for _, mod := range mods {
mod(model)
}
return model
}
func TestParseInput(t *testing.T) {
tests := []struct {
description string
argValues []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: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.Region = ""
}),
},
{
description: "supported only",
flagValues: map[string]string{
supportedFlag: "true",
},
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.Supported = true
model.Region = ""
}),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid)
})
}
}
func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
inputModel *inputModel
expectedRequest ske.ApiListProviderOptionsRequest
}{
{
description: "base",
inputModel: fixtureInputModel(),
expectedRequest: testClient.ListProviderOptions(testCtx, testRegion),
},
{
description: "base",
inputModel: fixtureInputModel(func(model *inputModel) {
model.Supported = true
}),
expectedRequest: testClient.ListProviderOptions(testCtx, testRegion).VersionState("SUPPORTED"),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
request := buildRequest(testCtx, testClient, tt.inputModel)
diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmpopts.EquateComparable(testCtx),
)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
func TestOutputResult(t *testing.T) {
type args struct {
model *inputModel
options *ske.ProviderOptions
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "empty",
args: args{},
wantErr: true,
},
{
name: "missing options",
args: args{
model: &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{},
},
},
wantErr: true,
},
{
name: "empty input model",
args: args{
model: &inputModel{},
options: &ske.ProviderOptions{},
},
wantErr: false,
},
{
name: "set model and options",
args: args{
model: &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{},
},
options: &ske.ProviderOptions{},
},
wantErr: false,
},
{
name: "empty values",
args: args{
model: &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{},
},
options: &ske.ProviderOptions{
KubernetesVersions: &[]ske.KubernetesVersion{},
},
},
wantErr: false,
},
{
name: "empty value in values",
args: args{
model: &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{},
},
options: &ske.ProviderOptions{
KubernetesVersions: &[]ske.KubernetesVersion{{}},
},
},
wantErr: false,
},
{
name: "valid values",
args: args{
model: &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{},
},
options: &ske.ProviderOptions{
KubernetesVersions: &[]ske.KubernetesVersion{
{
FeatureGates: &map[string]string{
"featureGate1": "foo",
"featureGate2": "bar",
},
State: utils.Ptr("supported"),
Version: utils.Ptr("0.00.0"),
},
{
ExpirationDate: utils.Ptr(time.Now()),
State: utils.Ptr("deprecated"),
Version: utils.Ptr("0.00.0"),
},
},
},
},
wantErr: false,
},
}
p := print.NewPrinter()
p.Cmd = NewCmd(&types.CmdParams{Printer: p})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := outputResult(p, tt.args.model, tt.args.options); (err != nil) != tt.wantErr {
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
package kubernetes_versions
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/stackitcloud/stackit-cli/internal/pkg/flags"
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
"github.com/stackitcloud/stackit-sdk-go/services/ske"
)
const (
supportedFlag = "supported"
)
type inputModel struct {
globalflags.GlobalFlagModel
Supported bool
}
func NewCmd(params *types.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: "kubernetes-versions",
Short: "Lists SKE provider options for kubernetes-versions",
Long: "Lists STACKIT Kubernetes Engine (SKE) provider options for kubernetes-versions.",
Args: args.NoArgs,
Example: examples.Build(
examples.NewExample(
`List SKE options for kubernetes-versions`,
"$ stackit ske options kubernetes-versions"),
examples.NewExample(
`List SKE options for supported kubernetes-versions`,
"$ stackit ske options kubernetes-versions --supported"),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(params.Printer, cmd, args)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion)
if err != nil {
return err
}
// Call API
req := buildRequest(ctx, apiClient, model)
resp, err := req.Execute()
if err != nil {
return fmt.Errorf("get SKE provider options: %w", err)
}
return outputResult(params.Printer, model, resp)
},
}
configureFlags(cmd)
return cmd
}
func configureFlags(cmd *cobra.Command) {
cmd.Flags().Bool(supportedFlag, false, "List supported versions only")
}
func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)
model := inputModel{
GlobalFlagModel: utils.PtrValue(globalFlags),
Supported: flags.FlagToBoolValue(p, cmd, supportedFlag),
}
p.DebugInputModel(model)
return &model, nil
}
func buildRequest(ctx context.Context, apiClient *ske.APIClient, model *inputModel) ske.ApiListProviderOptionsRequest {
req := apiClient.ListProviderOptions(ctx, model.Region)
if model.Supported {
req = req.VersionState("SUPPORTED")
}
return req
}
func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error {
if options == nil {
return fmt.Errorf("options is nil")
}
options.AvailabilityZones = nil
options.MachineImages = nil
options.MachineTypes = nil
options.VolumeTypes = nil
return p.OutputResult(model.OutputFormat, options, func() error {
versions := utils.PtrValue(options.KubernetesVersions)
table := tables.NewTable()
table.SetHeader("VERSION", "STATE", "EXPIRATION DATE", "FEATURE GATES")
for i := range versions {
v := versions[i]
featureGate, err := json.Marshal(utils.PtrValue(v.FeatureGates))
if err != nil {
return fmt.Errorf("marshal featureGates of Kubernetes version %q: %w", utils.PtrValue(v.Version), err)
}
expirationDate := ""
if v.ExpirationDate != nil {
expirationDate = v.ExpirationDate.Format(time.RFC3339)
}
table.AddRow(
utils.PtrString(v.Version),
utils.PtrString(v.State),
expirationDate,
string(featureGate))
}
err := table.Display(p)
if err != nil {
return fmt.Errorf("display output: %w", err)
}
return nil
})
}
package machine_images
import (
"context"
"testing"
"time"
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/testutils"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stackitcloud/stackit-sdk-go/services/ske"
)
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &ske.APIClient{}
const testRegion = "eu01"
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
globalflags.RegionFlag: testRegion,
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{
Region: testRegion,
Verbosity: globalflags.VerbosityDefault,
},
}
for _, mod := range mods {
mod(model)
}
return model
}
func TestParseInput(t *testing.T) {
tests := []struct {
description string
argValues []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: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.Region = ""
}),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid)
})
}
}
func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
inputModel *inputModel
expectedRequest ske.ApiListProviderOptionsRequest
}{
{
description: "base",
inputModel: fixtureInputModel(),
expectedRequest: testClient.ListProviderOptions(testCtx, testRegion),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
request := buildRequest(testCtx, testClient, tt.inputModel)
diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmpopts.EquateComparable(testCtx),
)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
func TestOutputResult(t *testing.T) {
type args struct {
model *inputModel
options *ske.ProviderOptions
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "empty",
args: args{},
wantErr: true,
},
{
name: "missing options",
args: args{
model: &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{},
},
},
wantErr: true,
},
{
name: "empty input model",
args: args{
model: &inputModel{},
options: &ske.ProviderOptions{},
},
wantErr: false,
},
{
name: "set model and options",
args: args{
model: &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{},
},
options: &ske.ProviderOptions{},
},
wantErr: false,
},
{
name: "empty values",
args: args{
model: &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{},
},
options: &ske.ProviderOptions{
MachineImages: &[]ske.MachineImage{},
},
},
wantErr: false,
},
{
name: "empty value in values",
args: args{
model: &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{},
},
options: &ske.ProviderOptions{
MachineImages: &[]ske.MachineImage{{}},
},
},
wantErr: false,
},
{
name: "valid values",
args: args{
model: &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{},
},
options: &ske.ProviderOptions{
MachineImages: &[]ske.MachineImage{
{
Name: utils.Ptr("image1"),
Versions: &[]ske.MachineImageVersion{
{
Cri: &[]ske.CRI{
{
Name: ske.CRINAME_CONTAINERD.Ptr(),
},
},
ExpirationDate: utils.Ptr(time.Now()),
State: utils.Ptr("supported"),
Version: utils.Ptr("0.00.0"),
},
},
},
{
Name: utils.Ptr("zone2"),
},
},
},
},
wantErr: false,
},
}
p := print.NewPrinter()
p.Cmd = NewCmd(&types.CmdParams{Printer: p})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := outputResult(p, tt.args.model, tt.args.options); (err != nil) != tt.wantErr {
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
package machine_images
import (
"context"
"fmt"
"strings"
"time"
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/ske"
)
type inputModel struct {
globalflags.GlobalFlagModel
}
func NewCmd(params *types.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: "machine-images",
Short: "Lists SKE provider options for machine-images",
Long: "Lists STACKIT Kubernetes Engine (SKE) provider options for machine-images.",
Args: args.NoArgs,
Example: examples.Build(
examples.NewExample(
`List SKE options for machine-images`,
"$ stackit ske options machine-images"),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(params.Printer, cmd, args)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion)
if err != nil {
return err
}
// Call API
req := buildRequest(ctx, apiClient, model)
resp, err := req.Execute()
if err != nil {
return fmt.Errorf("get SKE provider options: %w", err)
}
return outputResult(params.Printer, model, resp)
},
}
return cmd
}
func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)
model := inputModel{
GlobalFlagModel: utils.PtrValue(globalFlags),
}
p.DebugInputModel(model)
return &model, nil
}
func buildRequest(ctx context.Context, apiClient *ske.APIClient, model *inputModel) ske.ApiListProviderOptionsRequest {
req := apiClient.ListProviderOptions(ctx, model.Region)
return req
}
func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error {
if options == nil {
return fmt.Errorf("options is nil")
}
options.AvailabilityZones = nil
options.KubernetesVersions = nil
options.MachineTypes = nil
options.VolumeTypes = nil
return p.OutputResult(model.OutputFormat, options, func() error {
images := utils.PtrValue(options.MachineImages)
table := tables.NewTable()
table.SetHeader("NAME", "VERSION", "STATE", "EXPIRATION DATE", "SUPPORTED CRI")
for i := range images {
image := images[i]
versions := utils.PtrValue(image.Versions)
for j := range versions {
version := versions[j]
criNames := make([]string, 0)
for i := range utils.PtrValue(version.Cri) {
cri := utils.PtrValue(version.Cri)[i]
criNames = append(criNames, utils.PtrString(cri.Name))
}
criNamesString := strings.Join(criNames, ", ")
expirationDate := "-"
if version.ExpirationDate != nil {
expirationDate = version.ExpirationDate.Format(time.RFC3339)
}
table.AddRow(
utils.PtrString(image.Name),
utils.PtrString(version.Version),
utils.PtrString(version.State),
expirationDate,
criNamesString,
)
}
}
table.EnableAutoMergeOnColumns(1)
err := table.Display(p)
if err != nil {
return fmt.Errorf("display output: %w", err)
}
return nil
})
}
package machine_types
import (
"context"
"testing"
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/testutils"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stackitcloud/stackit-sdk-go/services/ske"
)
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &ske.APIClient{}
const testRegion = "eu01"
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
globalflags.RegionFlag: testRegion,
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{
Region: testRegion,
Verbosity: globalflags.VerbosityDefault,
},
}
for _, mod := range mods {
mod(model)
}
return model
}
func TestParseInput(t *testing.T) {
tests := []struct {
description string
argValues []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: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.Region = ""
}),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid)
})
}
}
func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
inputModel *inputModel
expectedRequest ske.ApiListProviderOptionsRequest
}{
{
description: "base",
inputModel: fixtureInputModel(),
expectedRequest: testClient.ListProviderOptions(testCtx, testRegion),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
request := buildRequest(testCtx, testClient, tt.inputModel)
diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmpopts.EquateComparable(testCtx),
)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
func TestOutputResult(t *testing.T) {
type args struct {
model *inputModel
options *ske.ProviderOptions
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "empty",
args: args{},
wantErr: true,
},
{
name: "missing options",
args: args{
model: &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{},
},
},
wantErr: true,
},
{
name: "empty input model",
args: args{
model: &inputModel{},
options: &ske.ProviderOptions{},
},
wantErr: false,
},
{
name: "set model and options",
args: args{
model: &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{},
},
options: &ske.ProviderOptions{},
},
wantErr: false,
},
{
name: "empty values",
args: args{
model: &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{},
},
options: &ske.ProviderOptions{
MachineTypes: &[]ske.MachineType{},
},
},
wantErr: false,
},
{
name: "empty value in values",
args: args{
model: &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{},
},
options: &ske.ProviderOptions{
MachineTypes: &[]ske.MachineType{{}},
},
},
wantErr: false,
},
{
name: "valid values",
args: args{
model: &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{},
},
options: &ske.ProviderOptions{
MachineTypes: &[]ske.MachineType{
{
Architecture: utils.Ptr("amd64"),
Cpu: utils.Ptr(int64(2)),
Gpu: utils.Ptr(int64(0)),
Memory: utils.Ptr(int64(16)),
Name: utils.Ptr("type1"),
},
{
Architecture: utils.Ptr("amd64"),
Cpu: utils.Ptr(int64(2)),
Gpu: utils.Ptr(int64(0)),
Memory: utils.Ptr(int64(16)),
Name: utils.Ptr("type2"),
},
},
},
},
wantErr: false,
},
}
p := print.NewPrinter()
p.Cmd = NewCmd(&types.CmdParams{Printer: p})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := outputResult(p, tt.args.model, tt.args.options); (err != nil) != tt.wantErr {
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
package machine_types
import (
"context"
"fmt"
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/ske"
)
type inputModel struct {
globalflags.GlobalFlagModel
}
func NewCmd(params *types.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: "machine-types",
Short: "Lists SKE provider options for machine-types",
Long: "Lists STACKIT Kubernetes Engine (SKE) provider options for machine-types.",
Args: args.NoArgs,
Example: examples.Build(
examples.NewExample(
`List SKE options for machine-types`,
"$ stackit ske options machine-types"),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(params.Printer, cmd, args)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion)
if err != nil {
return err
}
// Call API
req := buildRequest(ctx, apiClient, model)
resp, err := req.Execute()
if err != nil {
return fmt.Errorf("get SKE provider options: %w", err)
}
return outputResult(params.Printer, model, resp)
},
}
return cmd
}
func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)
model := inputModel{
GlobalFlagModel: utils.PtrValue(globalFlags),
}
p.DebugInputModel(model)
return &model, nil
}
func buildRequest(ctx context.Context, apiClient *ske.APIClient, model *inputModel) ske.ApiListProviderOptionsRequest {
req := apiClient.ListProviderOptions(ctx, model.Region)
return req
}
func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error {
if options == nil {
return fmt.Errorf("options is nil")
}
options.AvailabilityZones = nil
options.KubernetesVersions = nil
options.MachineImages = nil
options.VolumeTypes = nil
return p.OutputResult(model.OutputFormat, options, func() error {
machineTypes := utils.PtrValue(options.MachineTypes)
table := tables.NewTable()
table.SetHeader("TYPE", "CPU", "MEMORY")
for i := range machineTypes {
t := machineTypes[i]
table.AddRow(
utils.PtrString(t.Name),
utils.PtrString(t.Cpu),
utils.PtrString(t.Memory),
)
}
err := table.Display(p)
if err != nil {
return fmt.Errorf("display output: %w", err)
}
return nil
})
}
package volume_types
import (
"context"
"testing"
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/testutils"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stackitcloud/stackit-sdk-go/services/ske"
)
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &ske.APIClient{}
const testRegion = "eu01"
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
globalflags.RegionFlag: testRegion,
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{
Region: testRegion,
Verbosity: globalflags.VerbosityDefault,
},
}
for _, mod := range mods {
mod(model)
}
return model
}
func TestParseInput(t *testing.T) {
tests := []struct {
description string
argValues []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: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.Region = ""
}),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid)
})
}
}
func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
inputModel *inputModel
expectedRequest ske.ApiListProviderOptionsRequest
}{
{
description: "base",
inputModel: fixtureInputModel(),
expectedRequest: testClient.ListProviderOptions(testCtx, testRegion),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
request := buildRequest(testCtx, testClient, tt.inputModel)
diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmpopts.EquateComparable(testCtx),
)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
func TestOutputResult(t *testing.T) {
type args struct {
model *inputModel
options *ske.ProviderOptions
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "empty",
args: args{},
wantErr: true,
},
{
name: "missing options",
args: args{
model: &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{},
},
},
wantErr: true,
},
{
name: "empty input model",
args: args{
model: &inputModel{},
options: &ske.ProviderOptions{},
},
wantErr: false,
},
{
name: "set model and options",
args: args{
model: &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{},
},
options: &ske.ProviderOptions{},
},
wantErr: false,
},
{
name: "empty values",
args: args{
model: &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{},
},
options: &ske.ProviderOptions{
VolumeTypes: &[]ske.VolumeType{},
},
},
wantErr: false,
},
{
name: "empty value in values",
args: args{
model: &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{},
},
options: &ske.ProviderOptions{
VolumeTypes: &[]ske.VolumeType{{}},
},
},
wantErr: false,
},
{
name: "valid values",
args: args{
model: &inputModel{
GlobalFlagModel: globalflags.GlobalFlagModel{},
},
options: &ske.ProviderOptions{
VolumeTypes: &[]ske.VolumeType{
{
Name: utils.Ptr("type1"),
},
{
Name: utils.Ptr("type2"),
},
},
},
},
wantErr: false,
},
}
p := print.NewPrinter()
p.Cmd = NewCmd(&types.CmdParams{Printer: p})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := outputResult(p, tt.args.model, tt.args.options); (err != nil) != tt.wantErr {
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
package volume_types
import (
"context"
"fmt"
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
"github.com/stackitcloud/stackit-sdk-go/services/ske"
)
type inputModel struct {
globalflags.GlobalFlagModel
}
func NewCmd(params *types.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: "volume-types",
Short: "Lists SKE provider options for volume-types",
Long: "Lists STACKIT Kubernetes Engine (SKE) provider options for volume-types.",
Args: args.NoArgs,
Example: examples.Build(
examples.NewExample(
`List SKE options for volume-types`,
"$ stackit ske options volume-types"),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(params.Printer, cmd, args)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion)
if err != nil {
return err
}
// Call API
req := buildRequest(ctx, apiClient, model)
resp, err := req.Execute()
if err != nil {
return fmt.Errorf("get SKE provider options: %w", err)
}
return outputResult(params.Printer, model, resp)
},
}
return cmd
}
func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)
model := inputModel{
GlobalFlagModel: utils.PtrValue(globalFlags),
}
p.DebugInputModel(model)
return &model, nil
}
func buildRequest(ctx context.Context, apiClient *ske.APIClient, model *inputModel) ske.ApiListProviderOptionsRequest {
req := apiClient.ListProviderOptions(ctx, model.Region)
return req
}
func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error {
if options == nil {
return fmt.Errorf("options is nil")
}
options.AvailabilityZones = nil
options.KubernetesVersions = nil
options.MachineImages = nil
options.MachineTypes = nil
return p.OutputResult(model.OutputFormat, options, func() error {
volumeTypes := utils.PtrValue(options.VolumeTypes)
table := tables.NewTable()
table.SetHeader("TYPE")
for i := range volumeTypes {
z := volumeTypes[i]
table.AddRow(utils.PtrString(z.Name))
}
err := table.Display(p)
if err != nil {
return fmt.Errorf("display output: %w", err)
}
return nil
})
}
+1
-1

@@ -53,3 +53,3 @@ name: CI

- name: Check GoReleaser
uses: goreleaser/goreleaser-action@v6
uses: goreleaser/goreleaser-action@v7
with:

@@ -56,0 +56,0 @@ args: check

@@ -77,3 +77,3 @@ # STACKIT CLI release workflow.

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
uses: goreleaser/goreleaser-action@v7
with:

@@ -80,0 +80,0 @@ args: release --clean

@@ -140,21 +140,20 @@ version: 2

# Temporary disable snap release, see https://jira.schwarz/browse/STACKITCLI-330
# snapcrafts:
# # IDs of the builds for which to create packages for
# - ids:
# - linux-builds
# # The name of the snap
# name: stackit
# # The canonical title of the application, displayed in the software
# # centre graphical frontends
# title: STACKIT CLI
# summary: A command-line interface to manage STACKIT resources.
# description: "A command-line interface to manage STACKIT resources."
# license: Apache-2.0
# confinement: classic
# # Grade "devel" will only release to `edge` and `beta` channels
# # Grade "stable" will also release to the `candidate` and `stable` channels
# grade: stable
# # Whether to publish the Snap to the store
# publish: true
snapcrafts:
# IDs of the builds for which to create packages for
- ids:
- linux-builds
# The name of the snap
name: stackit
# The canonical title of the application, displayed in the software
# centre graphical frontends
title: STACKIT CLI
summary: A command-line interface to manage STACKIT resources.
description: "A command-line interface to manage STACKIT resources."
license: Apache-2.0
confinement: classic
# Grade "devel" will only release to `edge` and `beta` channels
# Grade "stable" will also release to the `candidate` and `stable` channels
grade: stable
# Whether to publish the Snap to the store
publish: true

@@ -161,0 +160,0 @@ winget:

@@ -34,4 +34,6 @@ ## stackit organization

* [stackit](./stackit.md) - Manage STACKIT resources using the command line
* [stackit organization describe](./stackit_organization_describe.md) - Show an organization
* [stackit organization list](./stackit_organization_list.md) - Lists all organizations
* [stackit organization member](./stackit_organization_member.md) - Manages organization members
* [stackit organization role](./stackit_organization_role.md) - Manages organization roles

@@ -7,2 +7,3 @@ ## stackit ske options

Command "options" is deprecated, use the subcommands instead.
Lists STACKIT Kubernetes Engine (SKE) provider options (availability zones, Kubernetes versions, machine images and types, volume types).

@@ -15,24 +16,6 @@ Pass one or more flags to filter what categories are shown.

### Examples
```
List SKE options for all categories
$ stackit ske options
List SKE options regarding Kubernetes versions only
$ stackit ske options --kubernetes-versions
List SKE options regarding Kubernetes versions and machine images
$ stackit ske options --kubernetes-versions --machine-images
```
### Options
```
--availability-zones Lists availability zones
-h, --help Help for "stackit ske options"
--kubernetes-versions Lists supported kubernetes versions
--machine-images Lists supported machine images
--machine-types Lists supported machine types
--volume-types Lists supported volume types
-h, --help Help for "stackit ske options"
```

@@ -54,2 +37,7 @@

* [stackit ske](./stackit_ske.md) - Provides functionality for SKE
* [stackit ske options availability-zones](./stackit_ske_options_availability-zones.md) - Lists SKE provider options for availability-zones
* [stackit ske options kubernetes-versions](./stackit_ske_options_kubernetes-versions.md) - Lists SKE provider options for kubernetes-versions
* [stackit ske options machine-images](./stackit_ske_options_machine-images.md) - Lists SKE provider options for machine-images
* [stackit ske options machine-types](./stackit_ske_options_machine-types.md) - Lists SKE provider options for machine-types
* [stackit ske options volume-types](./stackit_ske_options_volume-types.md) - Lists SKE provider options for volume-types
+19
-19
module github.com/stackitcloud/stackit-cli
go 1.24.0
go 1.25.0

@@ -18,6 +18,6 @@ require (

github.com/spf13/viper v1.21.0
github.com/stackitcloud/stackit-sdk-go/core v0.21.1
github.com/stackitcloud/stackit-sdk-go/services/alb v0.9.3
github.com/stackitcloud/stackit-sdk-go/core v0.22.0
github.com/stackitcloud/stackit-sdk-go/services/alb v0.10.0
github.com/stackitcloud/stackit-sdk-go/services/authorization v0.12.0
github.com/stackitcloud/stackit-sdk-go/services/cdn v1.9.4
github.com/stackitcloud/stackit-sdk-go/services/cdn v1.10.0
github.com/stackitcloud/stackit-sdk-go/services/dns v0.17.6

@@ -37,3 +37,3 @@ github.com/stackitcloud/stackit-sdk-go/services/edge v0.4.3

github.com/stackitcloud/stackit-sdk-go/services/serverupdate v1.2.6
github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.11.6
github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.12.0
github.com/stackitcloud/stackit-sdk-go/services/serviceenablement v1.2.7

@@ -43,8 +43,8 @@ github.com/stackitcloud/stackit-sdk-go/services/ske v1.7.0

github.com/zalando/go-keyring v0.2.6
golang.org/x/mod v0.32.0
golang.org/x/mod v0.33.0
golang.org/x/oauth2 v0.35.0
golang.org/x/term v0.39.0
golang.org/x/text v0.33.0
k8s.io/apimachinery v0.34.2
k8s.io/client-go v0.34.2
golang.org/x/term v0.40.0
golang.org/x/text v0.34.0
k8s.io/apimachinery v0.35.2
k8s.io/client-go v0.35.1
)

@@ -237,3 +237,3 @@

go.uber.org/zap v1.27.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect

@@ -246,2 +246,3 @@ golang.org/x/exp/typeparams v0.0.0-20251023183803-a4bb9ffd2546 // indirect

honnef.co/go/tools v0.6.1 // indirect
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect
mvdan.cc/gofumpt v0.9.2 // indirect

@@ -260,3 +261,2 @@ mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 // indirect

github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect

@@ -275,17 +275,17 @@ github.com/json-iterator/go v1.1.12 // indirect

github.com/stackitcloud/stackit-sdk-go/services/kms v1.3.2
github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.7.3
github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.8.0
github.com/stackitcloud/stackit-sdk-go/services/logme v0.25.6
github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.25.6
github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.4.5
github.com/stackitcloud/stackit-sdk-go/services/observability v0.16.3
github.com/stackitcloud/stackit-sdk-go/services/observability v0.17.0
github.com/stackitcloud/stackit-sdk-go/services/rabbitmq v0.26.0
github.com/stackitcloud/stackit-sdk-go/services/redis v0.25.6
github.com/stackitcloud/stackit-sdk-go/services/sfs v0.3.0
github.com/stackitcloud/stackit-sdk-go/services/sfs v0.4.0
github.com/subosito/gotenv v1.6.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/sys v0.41.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.34.2 // indirect
k8s.io/api v0.35.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/yaml v1.6.0 // indirect

@@ -292,0 +292,0 @@ )

@@ -191,4 +191,19 @@ # Installation

> We are currently working on distributing the CLI on a package manager for Windows. For the moment, please refer to one of the installation methods below.
#### Scoop
The STACKIT CLI can be installed through the [Scoop](https://scoop.sh/) package manager.
1. Install Scoop (if not already installed):
```powershell
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression
```
2. Install the CLI:
```powershell
scoop install stackit
```
## Manual installation

@@ -195,0 +210,0 @@

@@ -8,2 +8,3 @@ package describe

"github.com/stackitcloud/stackit-cli/internal/pkg/types"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"

@@ -229,2 +230,29 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"

},
{
name: "valid value",
args: args{
resp: &iaas.Image{
Id: utils.Ptr(uuid.NewString()),
Name: utils.Ptr("Image"),
Status: utils.Ptr("STATUS"),
DiskFormat: utils.Ptr("format"),
MinDiskSize: utils.Ptr(int64(0)),
MinRam: utils.Ptr(int64(0)),
Config: &iaas.ImageConfig{
Architecture: utils.Ptr("architecture"),
OperatingSystem: utils.Ptr("os"),
OperatingSystemDistro: iaas.NewNullableString(utils.Ptr("os distro")),
OperatingSystemVersion: iaas.NewNullableString(utils.Ptr("0.00.0")),
Uefi: utils.Ptr(true),
},
Labels: utils.Ptr(map[string]any{
"label1": true,
"label2": false,
"label3": 42,
"foo": "bar",
}),
},
},
wantErr: false,
},
}

@@ -231,0 +259,0 @@ p := print.NewPrinter()

@@ -99,5 +99,4 @@ package describe

table.AddRow("ID", *id)
table.AddSeparator()
}
table.AddSeparator()
if name := resp.Name; name != nil {

@@ -107,2 +106,6 @@ table.AddRow("NAME", *name)

}
if status := resp.Status; status != nil {
table.AddRow("STATUS", *status)
table.AddSeparator()
}
if format := resp.DiskFormat; format != nil {

@@ -109,0 +112,0 @@ table.AddRow("FORMAT", *format)

@@ -29,3 +29,3 @@ package create

var testPayload = &observability.CreateScrapeConfigPayload{
BasicAuth: &observability.CreateScrapeConfigPayloadBasicAuth{
BasicAuth: &observability.PartialUpdateScrapeConfigsRequestInnerBasicAuth{
Username: utils.Ptr("username"),

@@ -39,5 +39,5 @@ Password: utils.Ptr("password"),

JobName: utils.Ptr("default-name"),
MetricsRelabelConfigs: &[]observability.CreateScrapeConfigPayloadMetricsRelabelConfigsInner{
MetricsRelabelConfigs: &[]observability.PartialUpdateScrapeConfigsRequestInnerMetricsRelabelConfigsInner{
{
Action: observability.CREATESCRAPECONFIGPAYLOADMETRICSRELABELCONFIGSINNERACTION_REPLACE.Ptr(),
Action: observability.PARTIALUPDATESCRAPECONFIGSREQUESTINNERMETRICSRELABELCONFIGSINNERACTION_REPLACE.Ptr(),
Modulus: utils.Ptr(1.0),

@@ -59,3 +59,3 @@ Regex: utils.Ptr("regex"),

ScrapeTimeout: utils.Ptr("timeout"),
StaticConfigs: &[]observability.CreateScrapeConfigPayloadStaticConfigsInner{
StaticConfigs: &[]observability.PartialUpdateScrapeConfigsRequestInnerStaticConfigsInner{
{

@@ -69,3 +69,3 @@ Labels: &map[string]interface{}{

},
TlsConfig: &observability.CreateScrapeConfigPayloadHttpSdConfigsInnerOauth2TlsConfig{
TlsConfig: &observability.PartialUpdateScrapeConfigsRequestInnerHttpSdConfigsInnerOauth2TlsConfig{
InsecureSkipVerify: utils.Ptr(true),

@@ -72,0 +72,0 @@ },

@@ -27,3 +27,3 @@ package update

var testPayload = observability.UpdateScrapeConfigPayload{
BasicAuth: &observability.CreateScrapeConfigPayloadBasicAuth{
BasicAuth: &observability.PartialUpdateScrapeConfigsRequestInnerBasicAuth{
Username: utils.Ptr("username"),

@@ -36,5 +36,5 @@ Password: utils.Ptr("password"),

MetricsPath: utils.Ptr("/metrics"),
MetricsRelabelConfigs: &[]observability.CreateScrapeConfigPayloadMetricsRelabelConfigsInner{
MetricsRelabelConfigs: &[]observability.PartialUpdateScrapeConfigsRequestInnerMetricsRelabelConfigsInner{
{
Action: observability.CREATESCRAPECONFIGPAYLOADMETRICSRELABELCONFIGSINNERACTION_REPLACE.Ptr(),
Action: observability.PARTIALUPDATESCRAPECONFIGSREQUESTINNERMETRICSRELABELCONFIGSINNERACTION_REPLACE.Ptr(),
Modulus: utils.Ptr(1.0),

@@ -41,0 +41,0 @@ Regex: utils.Ptr("regex"),

@@ -8,2 +8,4 @@ package organization

"github.com/stackitcloud/stackit-cli/internal/cmd/organization/describe"
"github.com/stackitcloud/stackit-cli/internal/cmd/organization/list"
"github.com/stackitcloud/stackit-cli/internal/cmd/organization/member"

@@ -35,2 +37,4 @@ "github.com/stackitcloud/stackit-cli/internal/cmd/organization/role"

cmd.AddCommand(role.NewCmd(params))
cmd.AddCommand(list.NewCmd(params))
cmd.AddCommand(describe.NewCmd(params))
}

@@ -109,2 +109,12 @@ package create

{
description: "params flag missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, paramsFlag)
}),
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.Params = nil
}),
},
{
description: "project id missing",

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

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

}
parsedParams, err := runcommandUtils.ParseScriptParams(*model.Params)
var err error
model.Params, err = runcommandUtils.ParseScriptParams(model.Params)
if err != nil {

@@ -130,3 +132,2 @@ return nil, &cliErr.FlagValidationError{

}
model.Params = &parsedParams

@@ -133,0 +134,0 @@ p.DebugInputModel(model)

@@ -42,3 +42,3 @@ package options

model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{Region: testRegion, Verbosity: globalflags.VerbosityDefault},
GlobalFlagModel: globalflags.GlobalFlagModel{Region: testRegion, Verbosity: globalflags.VerbosityDefault},
AvailabilityZones: false,

@@ -58,3 +58,3 @@ KubernetesVersions: false,

model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{Region: testRegion, Verbosity: globalflags.VerbosityDefault},
GlobalFlagModel: globalflags.GlobalFlagModel{Region: testRegion, Verbosity: globalflags.VerbosityDefault},
AvailabilityZones: true,

@@ -192,3 +192,3 @@ KubernetesVersions: true,

model: &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{},
GlobalFlagModel: globalflags.GlobalFlagModel{},
},

@@ -199,3 +199,3 @@ },

{
name: "missing global flags in model",
name: "empty input model",
args: args{

@@ -205,3 +205,3 @@ model: &inputModel{},

},
wantErr: true,
wantErr: false,
},

@@ -212,3 +212,3 @@ {

model: &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{},
GlobalFlagModel: globalflags.GlobalFlagModel{},
},

@@ -215,0 +215,0 @@ options: &ske.ProviderOptions{},

@@ -10,2 +10,7 @@ package options

"github.com/stackitcloud/stackit-cli/internal/cmd/ske/options/availability_zones"
"github.com/stackitcloud/stackit-cli/internal/cmd/ske/options/kubernetes_versions"
"github.com/stackitcloud/stackit-cli/internal/cmd/ske/options/machine_images"
"github.com/stackitcloud/stackit-cli/internal/cmd/ske/options/machine_types"
"github.com/stackitcloud/stackit-cli/internal/cmd/ske/options/volume_types"
"github.com/stackitcloud/stackit-cli/internal/pkg/types"

@@ -15,3 +20,2 @@

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

@@ -35,3 +39,3 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"

type inputModel struct {
*globalflags.GlobalFlagModel
globalflags.GlobalFlagModel
AvailabilityZones bool

@@ -48,3 +52,4 @@ KubernetesVersions bool

Short: "Lists SKE provider options",
Long: fmt.Sprintf("%s\n%s",
Long: fmt.Sprintf("%s\n%s\n%s",
"Command \"options\" is deprecated, use the subcommands instead.",
"Lists STACKIT Kubernetes Engine (SKE) provider options (availability zones, Kubernetes versions, machine images and types, volume types).",

@@ -54,14 +59,5 @@ "Pass one or more flags to filter what categories are shown.",

Args: args.NoArgs,
Example: examples.Build(
examples.NewExample(
`List SKE options for all categories`,
"$ stackit ske options"),
examples.NewExample(
`List SKE options regarding Kubernetes versions only`,
"$ stackit ske options --kubernetes-versions"),
examples.NewExample(
`List SKE options regarding Kubernetes versions and machine images`,
"$ stackit ske options --kubernetes-versions --machine-images"),
),
RunE: func(cmd *cobra.Command, args []string) error {
params.Printer.Info("Command \"options\" is deprecated, use the subcommands instead.\n")
ctx := context.Background()

@@ -90,5 +86,14 @@ model, err := parseInput(params.Printer, cmd, args)

configureFlags(cmd)
addSubcommands(cmd, params)
return cmd
}
func addSubcommands(cmd *cobra.Command, params *types.CmdParams) {
cmd.AddCommand(availability_zones.NewCmd(params))
cmd.AddCommand(kubernetes_versions.NewCmd(params))
cmd.AddCommand(machine_images.NewCmd(params))
cmd.AddCommand(machine_types.NewCmd(params))
cmd.AddCommand(volume_types.NewCmd(params))
}
func configureFlags(cmd *cobra.Command) {

@@ -100,2 +105,8 @@ cmd.Flags().Bool(availabilityZonesFlag, false, "Lists availability zones")

cmd.Flags().Bool(volumeTypesFlag, false, "Lists supported volume types")
cobra.CheckErr(cmd.Flags().MarkDeprecated(availabilityZonesFlag, "This flag is deprecated and will be removed on 2026-09-26. Use the availability-zone subcommand instead."))
cobra.CheckErr(cmd.Flags().MarkDeprecated(kubernetesVersionsFlag, "This flag is deprecated and will be removed on 2026-09-26. Use the kubernetes-versions subcommand instead."))
cobra.CheckErr(cmd.Flags().MarkDeprecated(machineImagesFlag, "This flag is deprecated and will be removed on 2026-09-26. Use the machine-images subcommand instead."))
cobra.CheckErr(cmd.Flags().MarkDeprecated(machineTypesFlag, "This flag is deprecated and will be removed on 2026-09-26. Use the machine-types subcommand instead."))
cobra.CheckErr(cmd.Flags().MarkDeprecated(volumeTypesFlag, "This flag is deprecated and will be removed on 2026-09-26. Use the volume-types subcommand instead."))
}

@@ -121,3 +132,3 @@

model := inputModel{
GlobalFlagModel: globalFlags,
GlobalFlagModel: utils.PtrValue(globalFlags),
AvailabilityZones: availabilityZones,

@@ -140,3 +151,3 @@ KubernetesVersions: kubernetesVersions,

func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error {
if model == nil || model.GlobalFlagModel == nil {
if model == nil {
return fmt.Errorf("model is nil")

@@ -143,0 +154,0 @@ } else if options == nil {

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

"github.com/spf13/viper"
"github.com/stackitcloud/stackit-cli/internal/pkg/errors"

@@ -438,3 +439,9 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/fileutils"

err = fileutils.CopyFile(configFile, exportPath)
_, err = os.Stat(configFile)
if os.IsNotExist(err) {
// viper.SafeWriteConfigAs would not overwrite the target, so we use WriteConfigAs for the same behavior as CopyFile
err = viper.WriteConfigAs(exportPath)
} else {
err = fileutils.CopyFile(configFile, exportPath)
}
if err != nil {

@@ -441,0 +448,0 @@ return fmt.Errorf("export config file to %q: %w", exportPath, err)

@@ -92,3 +92,3 @@ package utils

payload := &observability.UpdateScrapeConfigPayload{
BasicAuth: &observability.CreateScrapeConfigPayloadBasicAuth{
BasicAuth: &observability.PartialUpdateScrapeConfigsRequestInnerBasicAuth{
Username: utils.Ptr("username"),

@@ -101,5 +101,5 @@ Password: utils.Ptr("password"),

MetricsPath: utils.Ptr("/metrics"),
MetricsRelabelConfigs: &[]observability.CreateScrapeConfigPayloadMetricsRelabelConfigsInner{
MetricsRelabelConfigs: &[]observability.PartialUpdateScrapeConfigsRequestInnerMetricsRelabelConfigsInner{
{
Action: utils.Ptr(observability.CreateScrapeConfigPayloadMetricsRelabelConfigsInnerAction("replace")),
Action: observability.PartialUpdateScrapeConfigsRequestInnerMetricsRelabelConfigsInnerGetActionAttributeType(utils.Ptr("replace")),
Modulus: utils.Ptr(1.0),

@@ -130,3 +130,3 @@ Regex: utils.Ptr("regex"),

},
TlsConfig: &observability.CreateScrapeConfigPayloadHttpSdConfigsInnerOauth2TlsConfig{
TlsConfig: &observability.PartialUpdateScrapeConfigsRequestInnerHttpSdConfigsInnerOauth2TlsConfig{
InsecureSkipVerify: utils.Ptr(true),

@@ -432,3 +432,3 @@ },

config *[]observability.MetricsRelabelConfig
expected *[]observability.CreateScrapeConfigPayloadMetricsRelabelConfigsInner
expected *[]observability.PartialUpdateScrapeConfigsRequestInnerMetricsRelabelConfigsInner
}{

@@ -448,5 +448,5 @@ {

},
expected: &[]observability.CreateScrapeConfigPayloadMetricsRelabelConfigsInner{
expected: &[]observability.PartialUpdateScrapeConfigsRequestInnerMetricsRelabelConfigsInner{
{
Action: observability.CREATESCRAPECONFIGPAYLOADMETRICSRELABELCONFIGSINNERACTION_REPLACE.Ptr(),
Action: observability.PARTIALUPDATESCRAPECONFIGSREQUESTINNERMETRICSRELABELCONFIGSINNERACTION_REPLACE.Ptr(),
Modulus: utils.Float64Ptr(1.0),

@@ -548,3 +548,3 @@ Regex: utils.Ptr("regex"),

auth *observability.BasicAuth
expected *observability.CreateScrapeConfigPayloadBasicAuth
expected *observability.PartialUpdateScrapeConfigsRequestInnerBasicAuth
}{

@@ -557,3 +557,3 @@ {

},
expected: &observability.CreateScrapeConfigPayloadBasicAuth{
expected: &observability.PartialUpdateScrapeConfigsRequestInnerBasicAuth{
Username: utils.Ptr("username"),

@@ -566,3 +566,3 @@ Password: utils.Ptr("password"),

auth: &observability.BasicAuth{},
expected: &observability.CreateScrapeConfigPayloadBasicAuth{},
expected: &observability.PartialUpdateScrapeConfigsRequestInnerBasicAuth{},
},

@@ -596,3 +596,3 @@ {

config *observability.TLSConfig
expected *observability.CreateScrapeConfigPayloadHttpSdConfigsInnerOauth2TlsConfig
expected *observability.PartialUpdateScrapeConfigsRequestInnerHttpSdConfigsInnerOauth2TlsConfig
}{

@@ -604,3 +604,3 @@ {

},
expected: &observability.CreateScrapeConfigPayloadHttpSdConfigsInnerOauth2TlsConfig{
expected: &observability.PartialUpdateScrapeConfigsRequestInnerHttpSdConfigsInnerOauth2TlsConfig{
InsecureSkipVerify: utils.Ptr(true),

@@ -612,3 +612,3 @@ },

config: &observability.TLSConfig{},
expected: &observability.CreateScrapeConfigPayloadHttpSdConfigsInnerOauth2TlsConfig{},
expected: &observability.PartialUpdateScrapeConfigsRequestInnerHttpSdConfigsInnerOauth2TlsConfig{},
},

@@ -615,0 +615,0 @@ {

@@ -24,3 +24,3 @@ package utils

var (
defaultStaticConfigs = []observability.CreateScrapeConfigPayloadStaticConfigsInner{
defaultStaticConfigs = []observability.PartialUpdateScrapeConfigsRequestInnerStaticConfigsInner{
{

@@ -124,10 +124,10 @@ Targets: utils.Ptr([]string{

func mapMetricsRelabelConfig(metricsRelabelConfigs *[]observability.MetricsRelabelConfig) *[]observability.CreateScrapeConfigPayloadMetricsRelabelConfigsInner {
func mapMetricsRelabelConfig(metricsRelabelConfigs *[]observability.MetricsRelabelConfig) *[]observability.PartialUpdateScrapeConfigsRequestInnerMetricsRelabelConfigsInner {
if metricsRelabelConfigs == nil {
return nil
}
var mappedConfigs []observability.CreateScrapeConfigPayloadMetricsRelabelConfigsInner
var mappedConfigs []observability.PartialUpdateScrapeConfigsRequestInnerMetricsRelabelConfigsInner
for _, config := range *metricsRelabelConfigs {
mappedConfig := observability.CreateScrapeConfigPayloadMetricsRelabelConfigsInner{
Action: observability.CreateScrapeConfigPayloadMetricsRelabelConfigsInnerGetActionAttributeType(config.Action),
mappedConfig := observability.PartialUpdateScrapeConfigsRequestInnerMetricsRelabelConfigsInner{
Action: observability.PartialUpdateScrapeConfigsRequestInnerMetricsRelabelConfigsInnerGetActionAttributeType(config.Action),
Modulus: utils.ConvertInt64PToFloat64P(config.Modulus),

@@ -165,3 +165,3 @@ Regex: config.Regex,

func mapBasicAuth(basicAuth *observability.BasicAuth) *observability.CreateScrapeConfigPayloadBasicAuth {
func mapBasicAuth(basicAuth *observability.BasicAuth) *observability.PartialUpdateScrapeConfigsRequestInnerBasicAuth {
if basicAuth == nil {

@@ -171,3 +171,3 @@ return nil

return &observability.CreateScrapeConfigPayloadBasicAuth{
return &observability.PartialUpdateScrapeConfigsRequestInnerBasicAuth{
Password: basicAuth.Password,

@@ -178,3 +178,3 @@ Username: basicAuth.Username,

func mapTlsConfig(tlsConfig *observability.TLSConfig) *observability.CreateScrapeConfigPayloadHttpSdConfigsInnerOauth2TlsConfig {
func mapTlsConfig(tlsConfig *observability.TLSConfig) *observability.PartialUpdateScrapeConfigsRequestInnerHttpSdConfigsInnerOauth2TlsConfig {
if tlsConfig == nil {

@@ -184,3 +184,3 @@ return nil

return &observability.CreateScrapeConfigPayloadHttpSdConfigsInnerOauth2TlsConfig{
return &observability.PartialUpdateScrapeConfigsRequestInnerHttpSdConfigsInnerOauth2TlsConfig{
InsecureSkipVerify: tlsConfig.InsecureSkipVerify,

@@ -187,0 +187,0 @@ }

@@ -5,2 +5,4 @@ package utils

"testing"
"github.com/google/go-cmp/cmp"
)

@@ -11,18 +13,24 @@

description string
input map[string]string
expectedOutput map[string]string
input *map[string]string
expectedOutput *map[string]string
isValid bool
}{
{
"base-ok",
map[string]string{"script": "ls /"},
map[string]string{"script": "ls /"},
true,
description: "base-ok",
input: &map[string]string{"script": "ls /"},
expectedOutput: &map[string]string{"script": "ls /"},
isValid: true,
},
{
"not-ok-nonexistant-file-specified-for-script",
map[string]string{"script": "@{/some/file/which/does/not/exist/and/thus/fails}"},
nil,
false,
description: "nil input",
input: nil,
expectedOutput: nil,
isValid: true,
},
{
description: "not-ok-nonexistant-file-specified-for-script",
input: &map[string]string{"script": "@{/some/file/which/does/not/exist/and/thus/fails}"},
expectedOutput: nil,
isValid: false,
},
}

@@ -43,4 +51,5 @@

}
if output["script"] != tt.expectedOutput["script"] {
t.Errorf("expected output to be %s, got %s", tt.expectedOutput["script"], output["script"])
diff := cmp.Diff(output, tt.expectedOutput)
if diff != "" {
t.Fatalf("ParseScriptParams() output mismatch (-want +got):\n%s", diff)
}

@@ -47,0 +56,0 @@ })

@@ -8,9 +8,11 @@ package utils

func ParseScriptParams(params map[string]string) (map[string]string, error) {
func ParseScriptParams(params *map[string]string) (*map[string]string, error) { //nolint:gocritic // flag value is a map pointer
if params == nil {
return nil, nil
}
parsed := map[string]string{}
for k, v := range params {
for k, v := range *params {
parsed[k] = v
if k == "script" && strings.HasPrefix(v, "@{") && strings.HasSuffix(v, "}") {

@@ -25,3 +27,4 @@ // Check if a script file path was specified, like: --params script=@{/tmp/test.sh}

}
return parsed, nil
return &parsed, nil
}

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