You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign 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 - go Package Compare versions

Comparing version
v0.1.0-apt-prerelease.1
to
v0.1.0-apt-prerelease.2
+45
docs/stackit_argus_plans.md
## stackit argus plans
Lists all Argus service plans
### Synopsis
Lists all Argus service plans.
```
stackit argus plans [flags]
```
### Examples
```
List all Argus service plans
$ stackit argus plans
List all Argus service plans in JSON format
$ stackit argus plans --output-format json
List up to 10 Argus service plans
$ stackit argus plans --limit 10
```
### Options
```
-h, --help Help for "stackit argus plans"
--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"]
-p, --project-id string Project ID
```
### SEE ALSO
* [stackit argus](./stackit_argus.md) - Provides functionality for Argus
## stackit argus
Provides functionality for Argus
### Synopsis
Provides functionality for Argus.
```
stackit argus [flags]
```
### Options
```
-h, --help Help for "stackit argus"
```
### 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"]
-p, --project-id string Project ID
```
### SEE ALSO
* [stackit](./stackit.md) - Manage STACKIT resources using the command line
* [stackit argus plans](./stackit_argus_plans.md) - Lists all Argus service plans
## stackit object-storage credentials create
Creates credentials for an Object Storage credentials group
### Synopsis
Creates credentials for an Object Storage credentials group. The credentials are only displayed upon creation, and will not be retrievable later.
```
stackit object-storage credentials create [flags]
```
### Examples
```
Create credentials for a credentials group with ID "xxx"
$ stackit object-storage credentials create --credentials-group-id xxx
Create credentials for a credentials group with ID "xxx", including a specific expiration date
$ stackit object-storage credentials create --credentials-group-id xxx --expire-date 2024-03-06T00:00:00.000Z
```
### Options
```
--credentials-group-id string Credentials Group ID
--expire-date string Expiration date for the credentials, in a date-time with the RFC3339 layout format, e.g. 2024-01-01T00:00:00Z
-h, --help Help for "stackit object-storage credentials create"
```
### 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"]
-p, --project-id string Project ID
```
### SEE ALSO
* [stackit object-storage credentials](./stackit_object-storage_credentials.md) - Provides functionality for Object Storage credentials
## stackit object-storage credentials delete
Deletes credentials of an Object Storage credentials group
### Synopsis
Deletes credentials of an Object Storage credentials group
```
stackit object-storage credentials delete CREDENTIALS_ID [flags]
```
### Examples
```
Delete a credential with ID "xxx" of credentials group with ID "yyy"
$ stackit object-storage credentials delete xxx --credentials-group-id yyy
```
### Options
```
--credentials-group-id string Credentials Group ID
-h, --help Help for "stackit object-storage credentials 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"]
-p, --project-id string Project ID
```
### SEE ALSO
* [stackit object-storage credentials](./stackit_object-storage_credentials.md) - Provides functionality for Object Storage credentials
## stackit object-storage credentials list
Lists all credentials for an Object Storage credentials group
### Synopsis
Lists all credentials for an Object Storage credentials group.
```
stackit object-storage credentials list [flags]
```
### Examples
```
List all credentials for a credentials group with ID "xxx"
$ stackit object-storage credentials list --credentials-group-id xxx
List all credentials for a credentials group with ID "xxx" in JSON format
$ stackit object-storage credentials list --credentials-group-id xxx --output-format json
List up to 10 credentials for a credentials group with ID "xxx"
$ stackit object-storage credentials list --credentials-group-id xxx --limit 10
```
### Options
```
--credentials-group-id string Credentials Group ID
-h, --help Help for "stackit object-storage credentials 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"]
-p, --project-id string Project ID
```
### SEE ALSO
* [stackit object-storage credentials](./stackit_object-storage_credentials.md) - Provides functionality for Object Storage credentials
## stackit object-storage credentials
Provides functionality for Object Storage credentials
### Synopsis
Provides functionality for Object Storage credentials.
```
stackit object-storage credentials [flags]
```
### Options
```
-h, --help Help for "stackit object-storage credentials"
```
### 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"]
-p, --project-id string Project ID
```
### SEE ALSO
* [stackit object-storage](./stackit_object-storage.md) - Provides functionality regarding Object Storage
* [stackit object-storage credentials create](./stackit_object-storage_credentials_create.md) - Creates credentials for an Object Storage credentials group
* [stackit object-storage credentials delete](./stackit_object-storage_credentials_delete.md) - Deletes credentials of an Object Storage credentials group
* [stackit object-storage credentials list](./stackit_object-storage_credentials_list.md) - Lists all credentials for an Object Storage credentials group
## stackit secrets-manager user create
Creates a Secrets Manager user
### Synopsis
Creates a Secrets Manager user.
The username and password are auto-generated and provided upon creation. The password cannot be retrieved later.
A description can be provided to identify a user.
```
stackit secrets-manager user create [flags]
```
### Examples
```
Create a Secrets Manager user for instance with ID "xxx" and description "yyy"
$ stackit secrets-manager user create --instance-id xxx --description yyy
Create a Secrets Manager user for instance with ID "xxx" with write access to the secrets engine
$ stackit secrets-manager user create --instance-id xxx --write
```
### Options
```
--description string A user chosen description to differentiate between multiple users
-h, --help Help for "stackit secrets-manager user create"
--instance-id string ID of the instance
--write User write access to the secrets engine. If unset, user is read-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"]
-p, --project-id string Project ID
```
### SEE ALSO
* [stackit secrets-manager user](./stackit_secrets-manager_user.md) - Provides functionality for Secrets Manager users
## stackit secrets-manager user delete
Deletes a Secrets Manager user
### Synopsis
Deletes a Secrets Manager user by ID. You can get the IDs of users for an instance by running:
$ stackit secrets-manager user delete --instance-id <INSTANCE_ID>
```
stackit secrets-manager user delete USER_ID [flags]
```
### Examples
```
Delete a Secrets Manager user with ID "xxx" for instance with ID "yyy"
$ stackit secrets-manager user delete xxx --instance-id yyy
```
### Options
```
-h, --help Help for "stackit secrets-manager user delete"
--instance-id string Instance ID
```
### 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"]
-p, --project-id string Project ID
```
### SEE ALSO
* [stackit secrets-manager user](./stackit_secrets-manager_user.md) - Provides functionality for Secrets Manager users
## stackit secrets-manager user describe
Shows details of a Secrets Manager user
### Synopsis
Shows details of a Secrets Manager user.
```
stackit secrets-manager user describe USER_ID [flags]
```
### Examples
```
Get details of a Secrets Manager user with ID "xxx" of instance with ID "yyy"
$ stackit secrets-manager user describe xxx --instance-id yyy
Get details of a Secrets Manager user with ID "xxx" of instance with ID "yyy" in table format
$ stackit secrets-manager user describe xxx --instance-id yyy --output-format pretty
```
### Options
```
-h, --help Help for "stackit secrets-manager user describe"
--instance-id string ID of the instance
```
### Options inherited from parent commands
```
-y, --assume-yes If set, skips all confirmation prompts
--async If set, runs the command asynchronously
-o, --output-format string Output format, one of ["json" "pretty"]
-p, --project-id string Project ID
```
### SEE ALSO
* [stackit secrets-manager user](./stackit_secrets-manager_user.md) - Provides functionality for Secrets Manager users
## stackit secrets-manager user list
Lists all Secrets Manager users
### Synopsis
Lists all Secrets Manager users.
```
stackit secrets-manager user list [flags]
```
### Examples
```
List all Secrets Manager users of instance with ID "xxx
$ stackit secrets-manager user list --instance-id xxx
List all Secrets Manager users in JSON format with ID "xxx
$ stackit secrets-manager user list --instance-id xxx --output-format json
List up to 10 Secrets Manager users with ID "xxx"
$ stackit secrets-manager user list --instance-id xxx --limit 10
```
### Options
```
-h, --help Help for "stackit secrets-manager user list"
--instance-id string Instance ID
--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"]
-p, --project-id string Project ID
```
### SEE ALSO
* [stackit secrets-manager user](./stackit_secrets-manager_user.md) - Provides functionality for Secrets Manager users
## stackit secrets-manager user update
Updates the write privileges Secrets Manager user
### Synopsis
Updates the write privileges Secrets Manager user.
```
stackit secrets-manager user update USER_ID [flags]
```
### Examples
```
Enable write access of a Secrets Manager user with ID "xxx" of instance with ID "yyy"
$ stackit secrets-manager user update xxx --instance-id yyy --enable-write
Disable write access of a Secrets Manager user with ID "xxx" of instance with ID "yyy"
$ stackit secrets-manager user update xxx --instance-id yyy --disable-write
```
### Options
```
--disable-write Set the user to have read-only access.
--enable-write Set the user to have write access.
-h, --help Help for "stackit secrets-manager user update"
--instance-id string ID of the instance
```
### Options inherited from parent commands
```
-y, --assume-yes If set, skips all confirmation prompts
--async If set, runs the command asynchronously
-o, --output-format string Output format, one of ["json" "pretty"]
-p, --project-id string Project ID
```
### SEE ALSO
* [stackit secrets-manager user](./stackit_secrets-manager_user.md) - Provides functionality for Secrets Manager users
## stackit secrets-manager user
Provides functionality for Secrets Manager users
### Synopsis
Provides functionality for Secrets Manager users.
```
stackit secrets-manager user [flags]
```
### Options
```
-h, --help Help for "stackit secrets-manager user"
```
### 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"]
-p, --project-id string Project ID
```
### SEE ALSO
* [stackit secrets-manager](./stackit_secrets-manager.md) - Provides functionality for Secrets Manager
* [stackit secrets-manager user create](./stackit_secrets-manager_user_create.md) - Creates a Secrets Manager user
* [stackit secrets-manager user delete](./stackit_secrets-manager_user_delete.md) - Deletes a Secrets Manager user
* [stackit secrets-manager user describe](./stackit_secrets-manager_user_describe.md) - Shows details of a Secrets Manager user
* [stackit secrets-manager user list](./stackit_secrets-manager_user_list.md) - Lists all Secrets Manager users
* [stackit secrets-manager user update](./stackit_secrets-manager_user_update.md) - Updates the write privileges Secrets Manager user
package argus
import (
"github.com/stackitcloud/stackit-cli/internal/cmd/argus/plans"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/spf13/cobra"
)
func NewCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "argus",
Short: "Provides functionality for Argus",
Long: "Provides functionality for Argus.",
Args: args.NoArgs,
Run: utils.CmdHelp,
}
addSubcommands(cmd)
return cmd
}
func addSubcommands(cmd *cobra.Command) {
cmd.AddCommand(plans.NewCmd())
}
package plans
import (
"context"
"testing"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"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/argus"
)
var projectIdFlag = globalflags.ProjectIdFlag
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &argus.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,
},
Limit: utils.Ptr(int64(10)),
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureRequest(mods ...func(request *argus.ApiListPlansRequest)) argus.ApiListPlansRequest {
request := testClient.ListPlans(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)
}
model, err := parseInput(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 argus.ApiListPlansRequest
}{
{
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 plans
import (
"context"
"encoding/json"
"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/flags"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/projectname"
"github.com/stackitcloud/stackit-cli/internal/pkg/services/argus/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-sdk-go/services/argus"
)
const (
limitFlag = "limit"
)
type inputModel struct {
*globalflags.GlobalFlagModel
Limit *int64
}
func NewCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "plans",
Short: "Lists all Argus service plans",
Long: "Lists all Argus service plans.",
Args: args.NoArgs,
Example: examples.Build(
examples.NewExample(
`List all Argus service plans`,
"$ stackit argus plans"),
examples.NewExample(
`List all Argus service plans in JSON format`,
"$ stackit argus plans --output-format json"),
examples.NewExample(
`List up to 10 Argus service plans`,
"$ stackit argus plans --limit 10"),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(cmd)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(cmd)
if err != nil {
return err
}
// Call API
req := buildRequest(ctx, model, apiClient)
resp, err := req.Execute()
if err != nil {
return fmt.Errorf("get Argus service plans: %w", err)
}
plans := *resp.Plans
if len(plans) == 0 {
projectLabel, err := projectname.GetProjectName(ctx, cmd)
if err != nil {
projectLabel = model.ProjectId
}
cmd.Printf("No plans found for project %q\n", projectLabel)
return nil
}
// Truncate output
if model.Limit != nil && len(plans) > int(*model.Limit) {
plans = plans[:*model.Limit]
}
return outputResult(cmd, model.OutputFormat, plans)
},
}
configureFlags(cmd)
return cmd
}
func configureFlags(cmd *cobra.Command) {
cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list")
}
func parseInput(cmd *cobra.Command) (*inputModel, error) {
globalFlags := globalflags.Parse(cmd)
if globalFlags.ProjectId == "" {
return nil, &errors.ProjectIdError{}
}
limit := flags.FlagToInt64Pointer(cmd, limitFlag)
if limit != nil && *limit < 1 {
return nil, &errors.FlagValidationError{
Flag: limitFlag,
Details: "must be greater than 0",
}
}
return &inputModel{
GlobalFlagModel: globalFlags,
Limit: limit,
}, nil
}
func buildRequest(ctx context.Context, model *inputModel, apiClient *argus.APIClient) argus.ApiListPlansRequest {
req := apiClient.ListPlans(ctx, model.ProjectId)
return req
}
func outputResult(cmd *cobra.Command, outputFormat string, plans []argus.Plan) error {
switch outputFormat {
case globalflags.JSONOutputFormat:
details, err := json.MarshalIndent(plans, "", " ")
if err != nil {
return fmt.Errorf("marshal Argus plans: %w", err)
}
cmd.Println(string(details))
return nil
default:
table := tables.NewTable()
table.SetHeader("ID", "PLAN NAME", "DESCRIPTION")
for i := range plans {
o := plans[i]
table.AddRow(*o.Id, *o.Name, *o.Description)
table.AddSeparator()
}
table.EnableAutoMergeOnColumns(1)
err := table.Display(cmd)
if err != nil {
return fmt.Errorf("render table: %w", err)
}
return nil
}
}
package create
import (
"context"
"testing"
"time"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"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/objectstorage"
)
var projectIdFlag = globalflags.ProjectIdFlag
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &objectstorage.APIClient{}
var testProjectId = uuid.NewString()
var testCredentialsGroupId = uuid.NewString()
var testExpirationDate = "2024-01-01T00:00:00Z"
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
projectIdFlag: testProjectId,
credentialsGroupIdFlag: testCredentialsGroupId,
expireDateFlag: testExpirationDate,
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
testExpirationDate, err := time.Parse(expirationTimeFormat, testExpirationDate)
if err != nil {
return &inputModel{}
}
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
ProjectId: testProjectId,
},
ExpireDate: utils.Ptr(testExpirationDate),
CredentialsGroupId: testCredentialsGroupId,
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixturePayload(mods ...func(payload *objectstorage.CreateAccessKeyPayload)) objectstorage.CreateAccessKeyPayload {
testExpirationDate, err := time.Parse(expirationTimeFormat, testExpirationDate)
if err != nil {
return objectstorage.CreateAccessKeyPayload{}
}
payload := objectstorage.CreateAccessKeyPayload{
Expires: utils.Ptr(testExpirationDate),
}
for _, mod := range mods {
mod(&payload)
}
return payload
}
func fixtureRequest(mods ...func(request *objectstorage.ApiCreateAccessKeyRequest)) objectstorage.ApiCreateAccessKeyRequest {
request := testClient.CreateAccessKey(testCtx, testProjectId)
request = request.CreateAccessKeyPayload(fixturePayload())
request = request.CredentialsGroup(testCredentialsGroupId)
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: "credentials group id missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, credentialsGroupIdFlag)
}),
isValid: false,
},
{
description: "credentials group id invalid 1",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[credentialsGroupIdFlag] = ""
}),
isValid: false,
},
{
description: "credentials group id invalid 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[credentialsGroupIdFlag] = "invalid-uuid"
}),
isValid: false,
},
{
description: "expiration date is missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, expireDateFlag)
}),
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.ExpireDate = nil
}),
},
{
description: "expiration date is empty",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[expireDateFlag] = ""
}),
isValid: false,
},
{
description: "expiration date is invalid",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[expireDateFlag] = "test"
}),
isValid: false,
},
{
description: "expiration date is invalid 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[expireDateFlag] = "11:00 12/12/2024"
}),
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
cmd := NewCmd()
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(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 objectstorage.ApiCreateAccessKeyRequest
}{
{
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 create
import (
"context"
"fmt"
"time"
objectStorageUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/utils"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/confirm"
"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/services/object-storage/client"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
)
const (
expireDateFlag = "expire-date"
credentialsGroupIdFlag = "credentials-group-id"
expirationTimeFormat = time.RFC3339
)
type inputModel struct {
*globalflags.GlobalFlagModel
ExpireDate *time.Time
CredentialsGroupId string
HidePassword bool
}
func NewCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "create",
Short: "Creates credentials for an Object Storage credentials group",
Long: "Creates credentials for an Object Storage credentials group. The credentials are only displayed upon creation, and will not be retrievable later.",
Args: args.NoArgs,
Example: examples.Build(
examples.NewExample(
`Create credentials for a credentials group with ID "xxx"`,
"$ stackit object-storage credentials create --credentials-group-id xxx"),
examples.NewExample(
`Create credentials for a credentials group with ID "xxx", including a specific expiration date`,
"$ stackit object-storage credentials create --credentials-group-id xxx --expire-date 2024-03-06T00:00:00.000Z"),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(cmd)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(cmd)
if err != nil {
return err
}
credentialsGroupLabel, err := objectStorageUtils.GetCredentialsGroupName(ctx, apiClient, model.ProjectId, model.CredentialsGroupId)
if err != nil {
credentialsGroupLabel = model.CredentialsGroupId
}
if !model.AssumeYes {
prompt := fmt.Sprintf("Are you sure you want to create credentials in group %q?", credentialsGroupLabel)
err = confirm.PromptForConfirmation(cmd, prompt)
if err != nil {
return err
}
}
// Call API
req := buildRequest(ctx, model, apiClient)
resp, err := req.Execute()
if err != nil {
return fmt.Errorf("create Object Storage credentials: %w", err)
}
expireDate := "Never"
if resp.Expires != nil && *resp.Expires != "" {
expireDate = *resp.Expires
}
cmd.Printf("Created credentials in group %q. Credentials ID: %s\n\n", credentialsGroupLabel, *resp.KeyId)
cmd.Printf("Access Key ID: %s\n", *resp.AccessKey)
cmd.Printf("Secret Access Key: %s\n", *resp.SecretAccessKey)
cmd.Printf("Expire Date: %s\n", expireDate)
return nil
},
}
configureFlags(cmd)
return cmd
}
func configureFlags(cmd *cobra.Command) {
cmd.Flags().String(expireDateFlag, "", "Expiration date for the credentials, in a date-time with the RFC3339 layout format, e.g. 2024-01-01T00:00:00Z")
cmd.Flags().Var(flags.UUIDFlag(), credentialsGroupIdFlag, "Credentials Group ID")
err := flags.MarkFlagsRequired(cmd, credentialsGroupIdFlag)
cobra.CheckErr(err)
}
func parseInput(cmd *cobra.Command) (*inputModel, error) {
globalFlags := globalflags.Parse(cmd)
if globalFlags.ProjectId == "" {
return nil, &errors.ProjectIdError{}
}
expireDate, err := flags.FlagToDateTimePointer(cmd, expireDateFlag, expirationTimeFormat)
if err != nil {
return nil, &errors.FlagValidationError{
Flag: expireDateFlag,
Details: err.Error(),
}
}
return &inputModel{
GlobalFlagModel: globalFlags,
ExpireDate: expireDate,
CredentialsGroupId: flags.FlagToStringValue(cmd, credentialsGroupIdFlag),
}, nil
}
func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiCreateAccessKeyRequest {
req := apiClient.CreateAccessKey(ctx, model.ProjectId)
req = req.CredentialsGroup(model.CredentialsGroupId)
req = req.CreateAccessKeyPayload(objectstorage.CreateAccessKeyPayload{
Expires: model.ExpireDate,
})
return req
}
package credentials
import (
"github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/credentials/create"
"github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/credentials/delete"
"github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/credentials/list"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/spf13/cobra"
)
func NewCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "credentials",
Short: "Provides functionality for Object Storage credentials",
Long: "Provides functionality for Object Storage credentials.",
Args: args.NoArgs,
Run: utils.CmdHelp,
}
addSubcommands(cmd)
return cmd
}
func addSubcommands(cmd *cobra.Command) {
cmd.AddCommand(create.NewCmd())
cmd.AddCommand(delete.NewCmd())
cmd.AddCommand(list.NewCmd())
}
package delete
import (
"context"
"testing"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
)
var projectIdFlag = globalflags.ProjectIdFlag
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &objectstorage.APIClient{}
var testProjectId = uuid.NewString()
var testCredentialsGroupId = uuid.NewString()
var testCredentialsId = "keyID"
func fixtureArgValues(mods ...func(argValues []string)) []string {
argValues := []string{
testCredentialsId,
}
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,
credentialsGroupIdFlag: testCredentialsGroupId,
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
ProjectId: testProjectId,
},
CredentialsGroupId: testCredentialsGroupId,
CredentialsId: testCredentialsId,
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureRequest(mods ...func(request *objectstorage.ApiDeleteAccessKeyRequest)) objectstorage.ApiDeleteAccessKeyRequest {
request := testClient.DeleteAccessKey(testCtx, testProjectId, testCredentialsId)
request = request.CredentialsGroup(testCredentialsGroupId)
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: "credentials group id missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, credentialsGroupIdFlag)
}),
isValid: false,
},
{
description: "credentials group id invalid 1",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[credentialsGroupIdFlag] = ""
}),
isValid: false,
},
{
description: "credentials group id invalid 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[credentialsGroupIdFlag] = "invalid-uuid"
}),
isValid: false,
},
{
description: "credential id invalid 1",
argValues: []string{""},
flagValues: fixtureFlagValues(),
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
cmd := NewCmd()
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(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 objectstorage.ApiDeleteAccessKeyRequest
}{
{
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/spf13/cobra"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/confirm"
"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/services/object-storage/client"
objectStorageUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/utils"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
)
const (
credentialsIdArg = "CREDENTIALS_ID" //nolint:gosec // linter false positive
credentialsGroupIdFlag = "credentials-group-id"
)
type inputModel struct {
*globalflags.GlobalFlagModel
CredentialsGroupId string
CredentialsId string
}
func NewCmd() *cobra.Command {
cmd := &cobra.Command{
Use: fmt.Sprintf("delete %s", credentialsIdArg),
Short: "Deletes credentials of an Object Storage credentials group",
Long: "Deletes credentials of an Object Storage credentials group",
Args: args.SingleArg(credentialsIdArg, nil),
Example: examples.Build(
examples.NewExample(
`Delete a credential with ID "xxx" of credentials group with ID "yyy"`,
"$ stackit object-storage credentials delete xxx --credentials-group-id yyy"),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(cmd, args)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(cmd)
if err != nil {
return err
}
credentialsGroupLabel, err := objectStorageUtils.GetCredentialsGroupName(ctx, apiClient, model.ProjectId, model.CredentialsGroupId)
if err != nil {
credentialsGroupLabel = model.CredentialsGroupId
}
credentialsLabel, err := objectStorageUtils.GetCredentialsName(ctx, apiClient, model.ProjectId, model.CredentialsGroupId, model.CredentialsId)
if err != nil {
credentialsLabel = model.CredentialsId
}
if !model.AssumeYes {
prompt := fmt.Sprintf("Are you sure you want to delete credentials %q of credentials group %q? (This cannot be undone)", credentialsLabel, credentialsGroupLabel)
err = confirm.PromptForConfirmation(cmd, prompt)
if err != nil {
return err
}
}
// Call API
req := buildRequest(ctx, model, apiClient)
_, err = req.Execute()
if err != nil {
return fmt.Errorf("delete Object Storage credentials: %w", err)
}
cmd.Printf("Deleted credentials %q of credentials group %q\n", credentialsLabel, credentialsGroupLabel)
return nil
},
}
configureFlags(cmd)
return cmd
}
func configureFlags(cmd *cobra.Command) {
cmd.Flags().Var(flags.UUIDFlag(), credentialsGroupIdFlag, "Credentials Group ID")
err := flags.MarkFlagsRequired(cmd, credentialsGroupIdFlag)
cobra.CheckErr(err)
}
func parseInput(cmd *cobra.Command, inputArgs []string) (*inputModel, error) {
credentialsId := inputArgs[0]
globalFlags := globalflags.Parse(cmd)
if globalFlags.ProjectId == "" {
return nil, &errors.ProjectIdError{}
}
return &inputModel{
GlobalFlagModel: globalFlags,
CredentialsGroupId: flags.FlagToStringValue(cmd, credentialsGroupIdFlag),
CredentialsId: credentialsId,
}, nil
}
func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiDeleteAccessKeyRequest {
req := apiClient.DeleteAccessKey(ctx, model.ProjectId, model.CredentialsId)
req = req.CredentialsGroup(model.CredentialsGroupId)
return req
}
package list
import (
"context"
"testing"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"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/objectstorage"
)
var projectIdFlag = globalflags.ProjectIdFlag
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &objectstorage.APIClient{}
var testProjectId = uuid.NewString()
var testCredentialsGroupId = uuid.NewString()
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
projectIdFlag: testProjectId,
credentialsGroupIdFlag: testCredentialsGroupId,
limitFlag: "10",
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
ProjectId: testProjectId,
},
CredentialsGroupId: testCredentialsGroupId,
Limit: utils.Ptr(int64(10)),
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureRequest(mods ...func(request *objectstorage.ApiListAccessKeysRequest)) objectstorage.ApiListAccessKeysRequest {
request := testClient.ListAccessKeys(testCtx, testProjectId)
request = request.CredentialsGroup(testCredentialsGroupId)
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: "credentials group id missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, credentialsGroupIdFlag)
}),
isValid: false,
},
{
description: "credentials group id invalid 1",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[credentialsGroupIdFlag] = ""
}),
isValid: false,
},
{
description: "credentials group id invalid 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[credentialsGroupIdFlag] = "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 := NewCmd()
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(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 objectstorage.ApiListAccessKeysRequest
}{
{
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"
objectStorageUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/utils"
"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/services/object-storage/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
)
const (
limitFlag = "limit"
credentialsGroupIdFlag = "credentials-group-id"
)
type inputModel struct {
*globalflags.GlobalFlagModel
CredentialsGroupId string
Limit *int64
}
func NewCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: "Lists all credentials for an Object Storage credentials group",
Long: "Lists all credentials for an Object Storage credentials group.",
Args: args.NoArgs,
Example: examples.Build(
examples.NewExample(
`List all credentials for a credentials group with ID "xxx"`,
"$ stackit object-storage credentials list --credentials-group-id xxx"),
examples.NewExample(
`List all credentials for a credentials group with ID "xxx" in JSON format`,
"$ stackit object-storage credentials list --credentials-group-id xxx --output-format json"),
examples.NewExample(
`List up to 10 credentials for a credentials group with ID "xxx"`,
"$ stackit object-storage credentials list --credentials-group-id xxx --limit 10"),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(cmd)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(cmd)
if err != nil {
return err
}
// Call API
req := buildRequest(ctx, model, apiClient)
resp, err := req.Execute()
if err != nil {
return fmt.Errorf("list Object Storage credentials: %w", err)
}
credentials := *resp.AccessKeys
if len(credentials) == 0 {
credentialsGroupLabel, err := objectStorageUtils.GetCredentialsGroupName(ctx, apiClient, model.ProjectId, model.CredentialsGroupId)
if err != nil {
credentialsGroupLabel = model.CredentialsGroupId
}
cmd.Printf("No credentials found for credentials group %q\n", credentialsGroupLabel)
return nil
}
// Truncate output
if model.Limit != nil && len(credentials) > int(*model.Limit) {
credentials = credentials[:*model.Limit]
}
return outputResult(cmd, model.OutputFormat, credentials)
},
}
configureFlags(cmd)
return cmd
}
func configureFlags(cmd *cobra.Command) {
cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list")
cmd.Flags().Var(flags.UUIDFlag(), credentialsGroupIdFlag, "Credentials Group ID")
err := flags.MarkFlagsRequired(cmd, credentialsGroupIdFlag)
cobra.CheckErr(err)
}
func parseInput(cmd *cobra.Command) (*inputModel, error) {
globalFlags := globalflags.Parse(cmd)
if globalFlags.ProjectId == "" {
return nil, &errors.ProjectIdError{}
}
limit := flags.FlagToInt64Pointer(cmd, limitFlag)
if limit != nil && *limit < 1 {
return nil, &errors.FlagValidationError{
Flag: limitFlag,
Details: "must be greater than 0",
}
}
return &inputModel{
GlobalFlagModel: globalFlags,
CredentialsGroupId: flags.FlagToStringValue(cmd, credentialsGroupIdFlag),
Limit: limit,
}, nil
}
func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiListAccessKeysRequest {
req := apiClient.ListAccessKeys(ctx, model.ProjectId)
req = req.CredentialsGroup(model.CredentialsGroupId)
return req
}
func outputResult(cmd *cobra.Command, outputFormat string, credentials []objectstorage.AccessKey) error {
switch outputFormat {
case globalflags.JSONOutputFormat:
details, err := json.MarshalIndent(credentials, "", " ")
if err != nil {
return fmt.Errorf("marshal Object Storage credentials list: %w", err)
}
cmd.Println(string(details))
return nil
default:
table := tables.NewTable()
table.SetHeader("CREDENTIALS ID", "ACCESS KEY ID", "EXPIRES AT")
for i := range credentials {
c := credentials[i]
expiresAt := "Never"
if c.Expires != nil {
expiresAt = *c.Expires
}
table.AddRow(*c.KeyId, *c.DisplayName, expiresAt)
}
err := table.Display(cmd)
if err != nil {
return fmt.Errorf("render table: %w", err)
}
return nil
}
}
package create
import (
"context"
"testing"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"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/secretsmanager"
)
var projectIdFlag = globalflags.ProjectIdFlag
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &secretsmanager.APIClient{}
var testProjectId = uuid.NewString()
var testInstanceId = uuid.NewString()
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
projectIdFlag: testProjectId,
instanceIdFlag: testInstanceId,
descriptionFlag: "sample description",
writeFlag: "false",
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
ProjectId: testProjectId,
},
InstanceId: testInstanceId,
Description: utils.Ptr("sample description"),
Write: utils.Ptr(false),
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureRequest(mods ...func(request *secretsmanager.ApiCreateUserRequest)) secretsmanager.ApiCreateUserRequest {
request := testClient.CreateUser(testCtx, testProjectId, testInstanceId)
request = request.CreateUserPayload(secretsmanager.CreateUserPayload{
Description: utils.Ptr("sample description"),
Write: utils.Ptr(false),
})
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 description specified",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, descriptionFlag)
}),
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.Description = utils.Ptr("")
}),
},
{
description: "no write flag given",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, writeFlag)
}),
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: "instance id missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, instanceIdFlag)
}),
isValid: false,
},
{
description: "instance id invalid 1",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[instanceIdFlag] = ""
}),
isValid: false,
},
{
description: "write set to true",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[writeFlag] = "true"
}),
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.Write = utils.Ptr(true)
}),
},
}
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)
}
model, err := parseInput(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 secretsmanager.ApiCreateUserRequest
}{
{
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 create
import (
"context"
"fmt"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/confirm"
"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/services/secrets-manager/client"
secretsManagerUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/secrets-manager/utils"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-sdk-go/services/secretsmanager"
)
const (
instanceIdFlag = "instance-id"
descriptionFlag = "description"
writeFlag = "write"
)
type inputModel struct {
*globalflags.GlobalFlagModel
InstanceId string
Description *string
Write *bool
}
func NewCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "create",
Short: "Creates a Secrets Manager user",
Long: fmt.Sprintf("%s\n%s\n%s",
"Creates a Secrets Manager user.",
"The username and password are auto-generated and provided upon creation. The password cannot be retrieved later.",
"A description can be provided to identify a user.",
),
Example: examples.Build(
examples.NewExample(
`Create a Secrets Manager user for instance with ID "xxx" and description "yyy"`,
"$ stackit secrets-manager user create --instance-id xxx --description yyy"),
examples.NewExample(
`Create a Secrets Manager user for instance with ID "xxx" with write access to the secrets engine`,
"$ stackit secrets-manager user create --instance-id xxx --write"),
),
Args: args.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(cmd)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(cmd)
if err != nil {
return err
}
instanceLabel, err := secretsManagerUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId)
if err != nil {
instanceLabel = model.InstanceId
}
if !model.AssumeYes {
prompt := fmt.Sprintf("Are you sure you want to create a user for instance %q?", instanceLabel)
err = confirm.PromptForConfirmation(cmd, prompt)
if err != nil {
return err
}
}
// Call API
req := buildRequest(ctx, model, apiClient)
resp, err := req.Execute()
if err != nil {
return fmt.Errorf("create Secrets Manager user: %w", err)
}
cmd.Printf("Created user for instance %q. User ID: %s\n\n", instanceLabel, *resp.Id)
cmd.Printf("Username: %s\n", *resp.Username)
cmd.Printf("Password: %s\n", *resp.Password)
cmd.Printf("Description: %s\n", *resp.Description)
cmd.Printf("Write Access: %t\n", *resp.Write)
return nil
},
}
configureFlags(cmd)
return cmd
}
func configureFlags(cmd *cobra.Command) {
cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "ID of the instance")
cmd.Flags().String(descriptionFlag, "", "A user chosen description to differentiate between multiple users")
cmd.Flags().Bool(writeFlag, false, "User write access to the secrets engine. If unset, user is read-only")
err := flags.MarkFlagsRequired(cmd, instanceIdFlag)
cobra.CheckErr(err)
}
func parseInput(cmd *cobra.Command) (*inputModel, error) {
globalFlags := globalflags.Parse(cmd)
if globalFlags.ProjectId == "" {
return nil, &errors.ProjectIdError{}
}
return &inputModel{
GlobalFlagModel: globalFlags,
InstanceId: flags.FlagToStringValue(cmd, instanceIdFlag),
Description: utils.Ptr(flags.FlagToStringValue(cmd, descriptionFlag)),
Write: utils.Ptr(flags.FlagToBoolValue(cmd, writeFlag)),
}, nil
}
func buildRequest(ctx context.Context, model *inputModel, apiClient *secretsmanager.APIClient) secretsmanager.ApiCreateUserRequest {
req := apiClient.CreateUser(ctx, model.ProjectId, model.InstanceId)
req = req.CreateUserPayload(secretsmanager.CreateUserPayload{
Description: model.Description,
Write: model.Write,
})
return req
}
package delete
import (
"context"
"testing"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/stackitcloud/stackit-sdk-go/services/secretsmanager"
)
var projectIdFlag = globalflags.ProjectIdFlag
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &secretsmanager.APIClient{}
var testProjectId = uuid.NewString()
var testInstanceId = uuid.NewString()
var testUserId = uuid.NewString()
func fixtureArgValues(mods ...func(argValues []string)) []string {
argValues := []string{
testUserId,
}
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,
instanceIdFlag: testInstanceId,
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
ProjectId: testProjectId,
},
InstanceId: testInstanceId,
UserId: testUserId,
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureRequest(mods ...func(request *secretsmanager.ApiDeleteUserRequest)) secretsmanager.ApiDeleteUserRequest {
request := testClient.DeleteUser(testCtx, testProjectId, testInstanceId, testUserId)
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,
},
{
description: "user id invalid 1",
argValues: []string{""},
flagValues: fixtureFlagValues(),
isValid: false,
},
{
description: "user id invalid 2",
argValues: []string{"invalid-uuid"},
flagValues: fixtureFlagValues(),
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
cmd := NewCmd()
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(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 secretsmanager.ApiDeleteUserRequest
}{
{
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/confirm"
"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/services/secrets-manager/client"
secretsManagerUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/secrets-manager/utils"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-sdk-go/services/secretsmanager"
)
const (
userIdArg = "USER_ID"
instanceIdFlag = "instance-id"
)
type inputModel struct {
*globalflags.GlobalFlagModel
InstanceId string
UserId string
}
func NewCmd() *cobra.Command {
cmd := &cobra.Command{
Use: fmt.Sprintf("delete %s", userIdArg),
Short: "Deletes a Secrets Manager user",
Long: fmt.Sprintf("%s\n%s",
"Deletes a Secrets Manager user by ID. You can get the IDs of users for an instance by running:",
" $ stackit secrets-manager user delete --instance-id <INSTANCE_ID>",
),
Example: examples.Build(
examples.NewExample(
`Delete a Secrets Manager user with ID "xxx" for instance with ID "yyy"`,
"$ stackit secrets-manager user delete xxx --instance-id yyy"),
),
Args: args.SingleArg(userIdArg, utils.ValidateUUID),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(cmd, args)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(cmd)
if err != nil {
return err
}
instanceLabel, err := secretsManagerUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId)
if err != nil {
instanceLabel = model.InstanceId
}
userLabel, err := secretsManagerUtils.GetUserLabel(ctx, apiClient, model.ProjectId, model.InstanceId, model.UserId)
if err != nil {
userLabel = fmt.Sprintf("%q", model.UserId)
}
if !model.AssumeYes {
prompt := fmt.Sprintf("Are you sure you want to delete user %s of instance %q? (This cannot be undone)", userLabel, instanceLabel)
err = confirm.PromptForConfirmation(cmd, prompt)
if err != nil {
return err
}
}
// Call API
req := buildRequest(ctx, model, apiClient)
err = req.Execute()
if err != nil {
return fmt.Errorf("delete Secrets Manager user: %w", err)
}
cmd.Printf("Deleted user %s of instance %q\n", userLabel, instanceLabel)
return nil
},
}
configureFlags(cmd)
return cmd
}
func configureFlags(cmd *cobra.Command) {
cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "Instance ID")
err := flags.MarkFlagsRequired(cmd, instanceIdFlag)
cobra.CheckErr(err)
}
func parseInput(cmd *cobra.Command, inputArgs []string) (*inputModel, error) {
userId := inputArgs[0]
globalFlags := globalflags.Parse(cmd)
if globalFlags.ProjectId == "" {
return nil, &errors.ProjectIdError{}
}
return &inputModel{
GlobalFlagModel: globalFlags,
InstanceId: flags.FlagToStringValue(cmd, instanceIdFlag),
UserId: userId,
}, nil
}
func buildRequest(ctx context.Context, model *inputModel, apiClient *secretsmanager.APIClient) secretsmanager.ApiDeleteUserRequest {
req := apiClient.DeleteUser(ctx, model.ProjectId, model.InstanceId, model.UserId)
return req
}
package describe
import (
"context"
"testing"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/stackitcloud/stackit-sdk-go/services/secretsmanager"
)
var projectIdFlag = globalflags.ProjectIdFlag
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &secretsmanager.APIClient{}
var testProjectId = uuid.NewString()
var testInstanceId = uuid.NewString()
var testUserId = uuid.NewString()
func fixtureArgValues(mods ...func(argValues []string)) []string {
argValues := []string{
testUserId,
}
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,
instanceIdFlag: testInstanceId,
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
ProjectId: testProjectId,
},
InstanceId: testInstanceId,
UserId: testUserId,
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureRequest(mods ...func(request *secretsmanager.ApiGetUserRequest)) secretsmanager.ApiGetUserRequest {
request := testClient.GetUser(testCtx, testProjectId, testInstanceId, testUserId)
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: fixtureArgValues(),
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 missing",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, instanceIdFlag)
}),
isValid: false,
},
{
description: "instance id invalid 1",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[instanceIdFlag] = ""
}),
isValid: false,
},
{
description: "instance id invalid 2",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[instanceIdFlag] = "invalid-uuid"
}),
isValid: false,
},
{
description: "user id invalid 1",
argValues: []string{""},
flagValues: fixtureFlagValues(),
isValid: false,
},
{
description: "user id invalid 2",
argValues: []string{"invalid-uuid"},
flagValues: fixtureFlagValues(),
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
cmd := NewCmd()
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(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 secretsmanager.ApiGetUserRequest
}{
{
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"
"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/services/secrets-manager/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/secretsmanager"
)
const (
userIdArg = "USER_ID"
instanceIdFlag = "instance-id"
)
type inputModel struct {
*globalflags.GlobalFlagModel
InstanceId string
UserId string
}
func NewCmd() *cobra.Command {
cmd := &cobra.Command{
Use: fmt.Sprintf("describe %s", userIdArg),
Short: "Shows details of a Secrets Manager user",
Long: "Shows details of a Secrets Manager user.",
Example: examples.Build(
examples.NewExample(
`Get details of a Secrets Manager user with ID "xxx" of instance with ID "yyy"`,
"$ stackit secrets-manager user describe xxx --instance-id yyy"),
examples.NewExample(
`Get details of a Secrets Manager user with ID "xxx" of instance with ID "yyy" in table format`,
"$ stackit secrets-manager user describe xxx --instance-id yyy --output-format pretty"),
),
Args: args.SingleArg(userIdArg, utils.ValidateUUID),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(cmd, args)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(cmd)
if err != nil {
return err
}
// Call API
req := buildRequest(ctx, model, apiClient)
resp, err := req.Execute()
if err != nil {
return fmt.Errorf("get Secrets Manager user: %w", err)
}
return outputResult(cmd, model.OutputFormat, *resp)
},
}
configureFlags(cmd)
return cmd
}
func configureFlags(cmd *cobra.Command) {
cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "ID of the instance")
err := flags.MarkFlagsRequired(cmd, instanceIdFlag)
cobra.CheckErr(err)
}
func parseInput(cmd *cobra.Command, inputArgs []string) (*inputModel, error) {
userId := inputArgs[0]
globalFlags := globalflags.Parse(cmd)
if globalFlags.ProjectId == "" {
return nil, &errors.ProjectIdError{}
}
return &inputModel{
GlobalFlagModel: globalFlags,
InstanceId: flags.FlagToStringValue(cmd, instanceIdFlag),
UserId: userId,
}, nil
}
func buildRequest(ctx context.Context, model *inputModel, apiClient *secretsmanager.APIClient) secretsmanager.ApiGetUserRequest {
req := apiClient.GetUser(ctx, model.ProjectId, model.InstanceId, model.UserId)
return req
}
func outputResult(cmd *cobra.Command, outputFormat string, user secretsmanager.User) error {
switch outputFormat {
case globalflags.PrettyOutputFormat:
table := tables.NewTable()
table.AddRow("ID", *user.Id)
table.AddSeparator()
table.AddRow("USERNAME", *user.Username)
table.AddSeparator()
table.AddRow("DESCRIPTION", *user.Description)
if user.Password != nil && *user.Password != "" {
table.AddSeparator()
table.AddRow("PASSWORD", *user.Password)
}
table.AddSeparator()
table.AddRow("WRITE ACCESS", *user.Write)
err := table.Display(cmd)
if err != nil {
return fmt.Errorf("render table: %w", err)
}
return nil
default:
details, err := json.MarshalIndent(user, "", " ")
if err != nil {
return fmt.Errorf("marshal Secrets Manager user: %w", err)
}
cmd.Println(string(details))
return nil
}
}
package list
import (
"context"
"testing"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"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/secretsmanager"
)
var projectIdFlag = globalflags.ProjectIdFlag
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &secretsmanager.APIClient{}
var testProjectId = uuid.NewString()
var testInstanceId = uuid.NewString()
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
projectIdFlag: testProjectId,
instanceIdFlag: testInstanceId,
limitFlag: "10",
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
ProjectId: testProjectId,
},
InstanceId: utils.Ptr(testInstanceId),
Limit: utils.Ptr(int64(10)),
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureRequest(mods ...func(request *secretsmanager.ApiListUsersRequest)) secretsmanager.ApiListUsersRequest {
request := testClient.ListUsers(testCtx, testProjectId, testInstanceId)
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: "instance id missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, instanceIdFlag)
}),
isValid: false,
},
{
description: "instance id invalid 1",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[instanceIdFlag] = ""
}),
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)
}
model, err := parseInput(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 secretsmanager.ApiListUsersRequest
}{
{
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/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/services/secrets-manager/client"
secretsManagerUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/secrets-manager/utils"
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-sdk-go/services/secretsmanager"
)
const (
instanceIdFlag = "instance-id"
limitFlag = "limit"
)
type inputModel struct {
*globalflags.GlobalFlagModel
InstanceId *string
Limit *int64
}
func NewCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: "Lists all Secrets Manager users",
Long: "Lists all Secrets Manager users.",
Args: args.NoArgs,
Example: examples.Build(
examples.NewExample(
`List all Secrets Manager users of instance with ID "xxx`,
"$ stackit secrets-manager user list --instance-id xxx"),
examples.NewExample(
`List all Secrets Manager users in JSON format with ID "xxx`,
"$ stackit secrets-manager user list --instance-id xxx --output-format json"),
examples.NewExample(
`List up to 10 Secrets Manager users with ID "xxx"`,
"$ stackit secrets-manager user list --instance-id xxx --limit 10"),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(cmd)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(cmd)
if err != nil {
return err
}
// Call API
req := buildRequest(ctx, model, apiClient)
resp, err := req.Execute()
if err != nil {
return fmt.Errorf("get Secrets Manager users: %w", err)
}
if resp.Users == nil || len(*resp.Users) == 0 {
instanceLabel, err := secretsManagerUtils.GetInstanceName(ctx, apiClient, model.ProjectId, *model.InstanceId)
if err != nil {
instanceLabel = *model.InstanceId
}
cmd.Printf("No users found for instance %q\n", instanceLabel)
return nil
}
users := *resp.Users
// Truncate output
if model.Limit != nil && len(users) > int(*model.Limit) {
users = users[:*model.Limit]
}
return outputResult(cmd, model.OutputFormat, users)
},
}
configureFlags(cmd)
return cmd
}
func configureFlags(cmd *cobra.Command) {
cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "Instance ID")
cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list")
err := flags.MarkFlagsRequired(cmd, instanceIdFlag)
cobra.CheckErr(err)
}
func parseInput(cmd *cobra.Command) (*inputModel, error) {
globalFlags := globalflags.Parse(cmd)
if globalFlags.ProjectId == "" {
return nil, &errors.ProjectIdError{}
}
limit := flags.FlagToInt64Pointer(cmd, limitFlag)
if limit != nil && *limit < 1 {
return nil, &errors.FlagValidationError{
Flag: limitFlag,
Details: "must be greater than 0",
}
}
return &inputModel{
GlobalFlagModel: globalFlags,
InstanceId: flags.FlagToStringPointer(cmd, instanceIdFlag),
Limit: flags.FlagToInt64Pointer(cmd, limitFlag),
}, nil
}
func buildRequest(ctx context.Context, model *inputModel, apiClient *secretsmanager.APIClient) secretsmanager.ApiListUsersRequest {
req := apiClient.ListUsers(ctx, model.ProjectId, *model.InstanceId)
return req
}
func outputResult(cmd *cobra.Command, outputFormat string, users []secretsmanager.User) error {
switch outputFormat {
case globalflags.JSONOutputFormat:
details, err := json.MarshalIndent(users, "", " ")
if err != nil {
return fmt.Errorf("marshal Secrets Manager user list: %w", err)
}
cmd.Println(string(details))
return nil
default:
table := tables.NewTable()
table.SetHeader("ID", "USERNAME", "DESCRIPTION", "WRITE ACCESS")
for i := range users {
user := users[i]
table.AddRow(*user.Id, *user.Username, *user.Description, *user.Write)
}
err := table.Display(cmd)
if err != nil {
return fmt.Errorf("render table: %w", err)
}
return nil
}
}
package update
import (
"context"
"testing"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"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/secretsmanager"
)
var projectIdFlag = globalflags.ProjectIdFlag
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &secretsmanager.APIClient{}
var testProjectId = uuid.NewString()
var testInstanceId = uuid.NewString()
var testUserId = uuid.NewString()
func fixtureArgValues(mods ...func(argValues []string)) []string {
argValues := []string{
testUserId,
}
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,
instanceIdFlag: testInstanceId,
enableWriteFlag: "true",
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
ProjectId: testProjectId,
},
InstanceId: testInstanceId,
UserId: testUserId,
EnableWrite: utils.Ptr(true),
DisableWrite: nil,
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureRequest(mods ...func(request *secretsmanager.ApiUpdateUserRequest)) secretsmanager.ApiUpdateUserRequest {
request := testClient.UpdateUser(testCtx, testProjectId, testInstanceId, testUserId)
request = request.UpdateUserPayload(secretsmanager.UpdateUserPayload{
Write: utils.Ptr(true),
})
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: fixtureArgValues(),
flagValues: map[string]string{},
isValid: false,
},
{
description: "no arg values",
argValues: []string{},
flagValues: fixtureFlagValues(),
isValid: false,
},
{
description: "disable write access",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[disableWriteFlag] = "true"
delete(flagValues, enableWriteFlag)
}),
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.EnableWrite = nil
model.DisableWrite = utils.Ptr(true)
}),
},
{
description: "neither write flag provided",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, enableWriteFlag)
delete(flagValues, disableWriteFlag)
}),
isValid: false,
},
{
description: "both flags provided",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[disableWriteFlag] = "true"
}),
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 missing",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, instanceIdFlag)
}),
isValid: false,
},
{
description: "instance id invalid 1",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[instanceIdFlag] = ""
}),
isValid: false,
},
{
description: "user id invalid 1",
argValues: []string{""},
flagValues: fixtureFlagValues(),
isValid: false,
},
{
description: "user id invalid 2",
argValues: []string{"invalid-uuid"},
flagValues: fixtureFlagValues(),
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
cmd := NewCmd()
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)
}
err = cmd.ValidateFlagGroups()
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error validating flag groups: %v", err)
}
model, err := parseInput(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 secretsmanager.ApiUpdateUserRequest
isValid bool
}{
{
description: "base",
model: fixtureInputModel(),
expectedRequest: fixtureRequest(),
isValid: true,
},
{
description: "disable write",
model: fixtureInputModel(func(model *inputModel) {
model.EnableWrite = nil
model.DisableWrite = utils.Ptr(true)
}),
expectedRequest: fixtureRequest().UpdateUserPayload(secretsmanager.UpdateUserPayload{
Write: utils.Ptr(false),
}),
isValid: true,
},
{
description: "both write flags set",
model: fixtureInputModel(func(model *inputModel) {
model.EnableWrite = utils.Ptr(true)
model.DisableWrite = utils.Ptr(true)
}),
isValid: false,
},
{
description: "none of the write flags set",
model: fixtureInputModel(func(model *inputModel) {
model.EnableWrite = nil
model.DisableWrite = nil
}),
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
request, err := buildRequest(testCtx, tt.model, testClient)
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 update
import (
"context"
"fmt"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/confirm"
"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/services/secrets-manager/client"
secretsManagerUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/secrets-manager/utils"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-sdk-go/services/secretsmanager"
)
const (
userIdArg = "USER_ID"
instanceIdFlag = "instance-id"
enableWriteFlag = "enable-write"
disableWriteFlag = "disable-write"
)
type inputModel struct {
*globalflags.GlobalFlagModel
InstanceId string
UserId string
EnableWrite *bool
DisableWrite *bool
}
func NewCmd() *cobra.Command {
cmd := &cobra.Command{
Use: fmt.Sprintf("update %s", userIdArg),
Short: "Updates the write privileges Secrets Manager user",
Long: "Updates the write privileges Secrets Manager user.",
Example: examples.Build(
examples.NewExample(
`Enable write access of a Secrets Manager user with ID "xxx" of instance with ID "yyy"`,
"$ stackit secrets-manager user update xxx --instance-id yyy --enable-write"),
examples.NewExample(
`Disable write access of a Secrets Manager user with ID "xxx" of instance with ID "yyy"`,
"$ stackit secrets-manager user update xxx --instance-id yyy --disable-write"),
),
Args: args.SingleArg(userIdArg, utils.ValidateUUID),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(cmd, args)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(cmd)
if err != nil {
return err
}
instanceLabel, err := secretsManagerUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId)
if err != nil {
instanceLabel = model.InstanceId
}
userLabel, err := secretsManagerUtils.GetUserLabel(ctx, apiClient, model.ProjectId, model.InstanceId, model.UserId)
if err != nil {
userLabel = fmt.Sprintf("%q", model.UserId)
}
if !model.AssumeYes {
prompt := fmt.Sprintf("Are you sure you want to update user %s of instance %q?", userLabel, instanceLabel)
err = confirm.PromptForConfirmation(cmd, prompt)
if err != nil {
return err
}
}
// Call API
req, err := buildRequest(ctx, model, apiClient)
if err != nil {
return err
}
err = req.Execute()
if err != nil {
return fmt.Errorf("update Secrets Manager user: %w", err)
}
cmd.Printf("Updated user %s of instance %q\n", userLabel, instanceLabel)
return nil
},
}
configureFlags(cmd)
return cmd
}
func configureFlags(cmd *cobra.Command) {
cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "ID of the instance")
cmd.Flags().Bool(enableWriteFlag, false, "Set the user to have write access.")
cmd.Flags().Bool(disableWriteFlag, false, "Set the user to have read-only access.")
err := flags.MarkFlagsRequired(cmd, instanceIdFlag)
cmd.MarkFlagsMutuallyExclusive(enableWriteFlag, disableWriteFlag)
cobra.CheckErr(err)
}
func parseInput(cmd *cobra.Command, inputArgs []string) (*inputModel, error) {
userId := inputArgs[0]
globalFlags := globalflags.Parse(cmd)
if globalFlags.ProjectId == "" {
return nil, &errors.ProjectIdError{}
}
enableWrite := flags.FlagToBoolPointer(cmd, enableWriteFlag)
disableWrite := flags.FlagToBoolPointer(cmd, disableWriteFlag)
if enableWrite == nil && disableWrite == nil {
return nil, &errors.EmptyUpdateError{}
}
return &inputModel{
GlobalFlagModel: globalFlags,
InstanceId: flags.FlagToStringValue(cmd, instanceIdFlag),
EnableWrite: enableWrite,
DisableWrite: disableWrite,
UserId: userId,
}, nil
}
func buildRequest(ctx context.Context, model *inputModel, apiClient *secretsmanager.APIClient) (secretsmanager.ApiUpdateUserRequest, error) {
req := apiClient.UpdateUser(ctx, model.ProjectId, model.InstanceId, model.UserId)
var write bool
if model.EnableWrite != nil && model.DisableWrite == nil {
write = true
} else if model.DisableWrite != nil && model.EnableWrite == nil {
write = false
} else if model.DisableWrite == nil && model.EnableWrite == nil {
// Should never happen
return req, fmt.Errorf("one of %s and %s flags needs to be set", enableWriteFlag, disableWriteFlag)
} else if model.DisableWrite != nil && model.EnableWrite != nil {
// Should never happen
return req, fmt.Errorf("%s and %s flags can't be both set", enableWriteFlag, disableWriteFlag)
}
req = req.UpdateUserPayload(secretsmanager.UpdateUserPayload{
Write: utils.Ptr(write),
})
return req, nil
}
package user
import (
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-cli/internal/cmd/secrets-manager/user/create"
"github.com/stackitcloud/stackit-cli/internal/cmd/secrets-manager/user/delete"
"github.com/stackitcloud/stackit-cli/internal/cmd/secrets-manager/user/describe"
"github.com/stackitcloud/stackit-cli/internal/cmd/secrets-manager/user/list"
"github.com/stackitcloud/stackit-cli/internal/cmd/secrets-manager/user/update"
)
func NewCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "user",
Short: "Provides functionality for Secrets Manager users",
Long: "Provides functionality for Secrets Manager users.",
Args: args.NoArgs,
Run: utils.CmdHelp,
}
addSubcommands(cmd)
return cmd
}
func addSubcommands(cmd *cobra.Command) {
cmd.AddCommand(list.NewCmd())
cmd.AddCommand(create.NewCmd())
cmd.AddCommand(delete.NewCmd())
cmd.AddCommand(describe.NewCmd())
cmd.AddCommand(update.NewCmd())
}
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/spf13/cobra"
"github.com/spf13/viper"
sdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config"
"github.com/stackitcloud/stackit-sdk-go/services/argus"
)
func ConfigureClient(cmd *cobra.Command) (*argus.APIClient, error) {
var err error
var apiClient *argus.APIClient
var cfgOptions []sdkConfig.ConfigurationOption
authCfgOption, err := auth.AuthenticationConfig(cmd, auth.AuthorizeUser)
if err != nil {
return nil, &errors.AuthError{}
}
cfgOptions = append(cfgOptions, authCfgOption, sdkConfig.WithRegion("eu01"))
customEndpoint := viper.GetString(config.ArgusCustomEndpointKey)
if customEndpoint != "" {
cfgOptions = append(cfgOptions, sdkConfig.WithEndpoint(customEndpoint))
}
apiClient, err = argus.NewAPIClient(cfgOptions...)
if err != nil {
return nil, &errors.AuthError{}
}
return apiClient, nil
}
+1
-0

@@ -72,2 +72,3 @@ # STACKIT CLI release workflow.

- name: Publish packages to APT repo
# Temporarily not skipping prereleases to test integration with APT
# if: contains(github.ref_name, '-') == false

@@ -74,0 +75,0 @@ env:

+2
-1

@@ -91,3 +91,4 @@ before:

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

@@ -94,0 +95,0 @@ snapcrafts:

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

```
--argus-custom-endpoint string Argus API base URL, used in calls to this API
--authorization-custom-endpoint string Authorization API base URL, used in calls to this API

@@ -34,0 +35,0 @@ --dns-custom-endpoint string DNS API base URL, used in calls to this API

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

```
--argus-custom-endpoint Argus API base URL. If unset, uses the default base URL
--async Configuration option to run commands asynchronously

@@ -31,0 +32,0 @@ --authorization-custom-endpoint Authorization API base URL. If unset, uses the default base URL

## stackit object-storage credentials-group delete
Deletes a credentials group
Deletes a credentials group that holds Object Storage access credentials
### Synopsis
Deletes a credentials group. Only possible if there are no valid credentials (access-keys) left in the group, otherwise it will throw an error.
Deletes a credentials group that holds Object Storage access credentials. Only possible if there are no valid credentials (access-keys) left in the group, otherwise it will throw an error.

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

## stackit object-storage credentials-group list
Lists all credentials groups
Lists all credentials groups that hold Object Storage access credentials
### Synopsis
Lists all credentials groups.
Lists all credentials groups that hold Object Storage access credentials.

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

@@ -32,4 +32,4 @@ ## stackit object-storage credentials-group

* [stackit object-storage credentials-group create](./stackit_object-storage_credentials-group_create.md) - Creates a credentials group to hold Object Storage access credentials
* [stackit object-storage credentials-group delete](./stackit_object-storage_credentials-group_delete.md) - Deletes a credentials group
* [stackit object-storage credentials-group list](./stackit_object-storage_credentials-group_list.md) - Lists all credentials groups
* [stackit object-storage credentials-group delete](./stackit_object-storage_credentials-group_delete.md) - Deletes a credentials group that holds Object Storage access credentials
* [stackit object-storage credentials-group list](./stackit_object-storage_credentials-group_list.md) - Lists all credentials groups that hold Object Storage access credentials

@@ -32,2 +32,3 @@ ## stackit object-storage

* [stackit object-storage bucket](./stackit_object-storage_bucket.md) - Provides functionality for Object Storage buckets
* [stackit object-storage credentials](./stackit_object-storage_credentials.md) - Provides functionality for Object Storage credentials
* [stackit object-storage credentials-group](./stackit_object-storage_credentials-group.md) - Provides functionality for Object Storage credentials group

@@ -34,0 +35,0 @@ * [stackit object-storage disable](./stackit_object-storage_disable.md) - Disables Object Storage for a project

@@ -32,2 +32,3 @@ ## stackit secrets-manager

* [stackit secrets-manager instance](./stackit_secrets-manager_instance.md) - Provides functionality for Secrets Manager instances
* [stackit secrets-manager user](./stackit_secrets-manager_user.md) - Provides functionality for Secrets Manager users

@@ -28,2 +28,3 @@ ## stackit

* [stackit argus](./stackit_argus.md) - Provides functionality for Argus
* [stackit auth](./stackit_auth.md) - Provides authentication functionality

@@ -30,0 +31,0 @@ * [stackit config](./stackit_config.md) - Provides functionality for CLI configuration options

+3
-2

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

require (
github.com/golang-jwt/jwt/v5 v5.2.0
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/go-cmp v0.6.0

@@ -26,3 +26,3 @@ github.com/google/uuid v1.6.0

golang.org/x/mod v0.15.0
golang.org/x/oauth2 v0.17.0
golang.org/x/oauth2 v0.18.0
golang.org/x/text v0.14.0

@@ -53,2 +53,3 @@ )

github.com/spf13/cast v1.6.0 // indirect
github.com/stackitcloud/stackit-sdk-go/services/argus v0.9.5
github.com/stackitcloud/stackit-sdk-go/services/logme v0.10.1

@@ -55,0 +56,0 @@ github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.10.1

+6
-4

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

github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=

@@ -79,2 +79,4 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=

github.com/stackitcloud/stackit-sdk-go/core v0.10.0/go.mod h1:B5dkVm2HlBRG7liBVIFNqncDb6TUHnJ7t0GsKhAFuRk=
github.com/stackitcloud/stackit-sdk-go/services/argus v0.9.5 h1:RLsA2jO9sMNpn7NYdtFyumY5Vj4n4WtBm2J2NBKlsnw=
github.com/stackitcloud/stackit-sdk-go/services/argus v0.9.5/go.mod h1:lzGbqwV0hqeX/kUvaaFTgjOJRxUlsZ911TX1YAcKwqc=
github.com/stackitcloud/stackit-sdk-go/services/authorization v0.1.1 h1:h7dCaBlbU34WSGuEXREmCdCzQafZgdXDZuairAzeuo8=

@@ -133,4 +135,4 @@ github.com/stackitcloud/stackit-sdk-go/services/authorization v0.1.1/go.mod h1:V+wTIfuJRV8PiSOfMX6GCTaHWltGaLCz8ImOKeHIaIA=

golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=
golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

@@ -137,0 +139,0 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

@@ -23,7 +23,34 @@ # Installation

We are working on distributing the CLI using a package manager for Linux. For the moment, you can either install via [Homebrew](https://brew.sh/) or refer to one of the installation methods below.
#### Debian/Ubuntu (`APT`)
The STACKIT CLI can be installed through the [`APT`](https://ubuntu.com/server/docs/package-management) package manager.
1. Import the STACKIT public key:
```shell
curl https://object.storage.eu01.onstackit.cloud/stackit-public-key/key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/stackit.gpg
```
2. Add the STACKIT CLI package repository as a package source:
```shell
echo "deb [signed-by=/usr/share/keyrings/stackit.gpg] https://object.storage.eu01.onstackit.cloud/stackit-cli-apt stackit main" | sudo tee -a /etc/apt/sources.list.d/stackit.list
```
3. Update repository information and install the `stackit` package:
```shell
sudo apt-get update
sudo apt-get install stackit
```
#### Any distribution
Alternatively, you can install via [Homebrew](https://brew.sh/) or refer to one of the installation methods below.
> We are currently working on distributing the CLI on more package managers for Linux.
### Windows
We are working on distributing the CLI using a package manager for Windows. For the moment, please refer to one of the installation methods below.
> 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.

@@ -30,0 +57,0 @@ ## Manual installation

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

argusCustomEndpointFlag = "argus-custom-endpoint"
authorizationCustomEndpointFlag = "authorization-custom-endpoint"

@@ -96,2 +97,3 @@ dnsCustomEndpointFlag = "dns-custom-endpoint"

cmd.Flags().String(argusCustomEndpointFlag, "", "Argus API base URL, used in calls to this API")
cmd.Flags().String(authorizationCustomEndpointFlag, "", "Authorization API base URL, used in calls to this API")

@@ -112,4 +114,6 @@ cmd.Flags().String(dnsCustomEndpointFlag, "", "DNS API base URL, used in calls to this API")

err := viper.BindPFlag(config.AuthorizationCustomEndpointKey, cmd.Flags().Lookup(authorizationCustomEndpointFlag))
err := viper.BindPFlag(config.ArgusCustomEndpointKey, cmd.Flags().Lookup(argusCustomEndpointFlag))
cobra.CheckErr(err)
err = viper.BindPFlag(config.AuthorizationCustomEndpointKey, cmd.Flags().Lookup(authorizationCustomEndpointFlag))
cobra.CheckErr(err)
err = viper.BindPFlag(config.DNSCustomEndpointKey, cmd.Flags().Lookup(dnsCustomEndpointFlag))

@@ -116,0 +120,0 @@ cobra.CheckErr(err)

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

argusCustomEndpointFlag: true,
authorizationCustomEndpointFlag: true,

@@ -40,2 +41,3 @@ dnsCustomEndpointFlag: true,

ArgusCustomEndpoint: true,
AuthorizationCustomEndpoint: true,

@@ -81,2 +83,3 @@ DNSCustomEndpoint: true,

model.ArgusCustomEndpoint = false
model.AuthorizationCustomEndpoint = false

@@ -117,2 +120,12 @@ model.DNSCustomEndpoint = false

{
description: "argus custom endpoint empty",
flagValues: fixtureFlagValues(func(flagValues map[string]bool) {
flagValues[argusCustomEndpointFlag] = false
}),
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.ArgusCustomEndpoint = false
}),
},
{
description: "dns custom endpoint empty",

@@ -119,0 +132,0 @@ flagValues: fixtureFlagValues(func(flagValues map[string]bool) {

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

argusCustomEndpointFlag = "argus-custom-endpoint"
authorizationCustomEndpointFlag = "authorization-custom-endpoint"

@@ -47,2 +48,3 @@ dnsCustomEndpointFlag = "dns-custom-endpoint"

ArgusCustomEndpoint bool
AuthorizationCustomEndpoint bool

@@ -98,2 +100,5 @@ DNSCustomEndpoint bool

if model.ArgusCustomEndpoint {
viper.Set(config.ArgusCustomEndpointKey, "")
}
if model.AuthorizationCustomEndpoint {

@@ -160,2 +165,3 @@ viper.Set(config.AuthorizationCustomEndpointKey, "")

cmd.Flags().Bool(argusCustomEndpointFlag, false, "Argus API base URL. If unset, uses the default base URL")
cmd.Flags().Bool(authorizationCustomEndpointFlag, false, "Authorization API base URL. If unset, uses the default base URL")

@@ -184,2 +190,3 @@ cmd.Flags().Bool(dnsCustomEndpointFlag, false, "DNS API base URL. If unset, uses the default base URL")

SessionTimeLimit: flags.FlagToBoolValue(cmd, sessionTimeLimitFlag),
ArgusCustomEndpoint: flags.FlagToBoolValue(cmd, argusCustomEndpointFlag),
AuthorizationCustomEndpoint: flags.FlagToBoolValue(cmd, authorizationCustomEndpointFlag),

@@ -186,0 +193,0 @@ DNSCustomEndpoint: flags.FlagToBoolValue(cmd, dnsCustomEndpointFlag),

@@ -23,8 +23,8 @@ package create

var testProjectId = uuid.NewString()
var testDisplayName = "test-name"
var testCredentialsGroupName = "test-name"
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
projectIdFlag: testProjectId,
nameFlag: testDisplayName,
projectIdFlag: testProjectId,
credentialsGroupNameFlag: testCredentialsGroupName,
}

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

},
DisplayName: testDisplayName,
CredentialsGroupName: testCredentialsGroupName,
}

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

payload := objectstorage.CreateCredentialsGroupPayload{
DisplayName: utils.Ptr(testDisplayName),
DisplayName: utils.Ptr(testCredentialsGroupName),
}

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

flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, nameFlag)
delete(flagValues, credentialsGroupNameFlag)
}),

@@ -116,0 +116,0 @@ isValid: false,

@@ -21,3 +21,3 @@ package create

const (
nameFlag = "name"
credentialsGroupNameFlag = "name"
)

@@ -27,3 +27,3 @@

*globalflags.GlobalFlagModel
DisplayName string
CredentialsGroupName string
}

@@ -56,3 +56,3 @@

if !model.AssumeYes {
prompt := fmt.Sprintf("Are you sure you want to create a credentials group with name %q?", model.DisplayName)
prompt := fmt.Sprintf("Are you sure you want to create a credentials group with name %q?", model.CredentialsGroupName)
err = confirm.PromptForConfirmation(cmd, prompt)

@@ -81,5 +81,5 @@ if err != nil {

func configureFlags(cmd *cobra.Command) {
cmd.Flags().String(nameFlag, "", "Name of the group holding credentials")
cmd.Flags().String(credentialsGroupNameFlag, "", "Name of the group holding credentials")
err := flags.MarkFlagsRequired(cmd, nameFlag)
err := flags.MarkFlagsRequired(cmd, credentialsGroupNameFlag)
cobra.CheckErr(err)

@@ -95,4 +95,4 @@ }

return &inputModel{
GlobalFlagModel: globalFlags,
DisplayName: flags.FlagToStringValue(cmd, nameFlag),
GlobalFlagModel: globalFlags,
CredentialsGroupName: flags.FlagToStringValue(cmd, credentialsGroupNameFlag),
}, nil

@@ -104,5 +104,5 @@ }

req = req.CreateCredentialsGroupPayload(objectstorage.CreateCredentialsGroupPayload{
DisplayName: utils.Ptr(model.DisplayName),
DisplayName: utils.Ptr(model.CredentialsGroupName),
})
return req
}

@@ -32,4 +32,4 @@ package delete

Use: fmt.Sprintf("delete %s", credentialsGroupIdArg),
Short: "Deletes a credentials group",
Long: "Deletes a credentials group. Only possible if there are no valid credentials (access-keys) left in the group, otherwise it will throw an error.",
Short: "Deletes a credentials group that holds Object Storage access credentials",
Long: "Deletes a credentials group that holds Object Storage access credentials. Only possible if there are no valid credentials (access-keys) left in the group, otherwise it will throw an error.",
Args: args.SingleArg(credentialsGroupIdArg, utils.ValidateUUID),

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

@@ -32,4 +32,4 @@ package list

Use: "list",
Short: "Lists all credentials groups",
Long: "Lists all credentials groups.",
Short: "Lists all credentials groups that hold Object Storage access credentials",
Long: "Lists all credentials groups that hold Object Storage access credentials.",
Args: args.NoArgs,

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

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

"github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/bucket"
"github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/credentials"
credentialsGroup "github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/credentials-group"

@@ -32,2 +33,3 @@ "github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/disable"

cmd.AddCommand(credentialsGroup.NewCmd())
cmd.AddCommand(credentials.NewCmd())
}

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

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

@@ -88,2 +89,3 @@ "github.com/stackitcloud/stackit-cli/internal/cmd/config"

func addSubcommands(cmd *cobra.Command) {
cmd.AddCommand(argus.NewCmd())
cmd.AddCommand(auth.NewCmd())

@@ -90,0 +92,0 @@ cmd.AddCommand(config.NewCmd())

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

"github.com/stackitcloud/stackit-cli/internal/cmd/secrets-manager/instance"
"github.com/stackitcloud/stackit-cli/internal/cmd/secrets-manager/user"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"

@@ -26,2 +27,3 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/utils"

cmd.AddCommand(instance.NewCmd())
cmd.AddCommand(user.NewCmd())
}

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

ArgusCustomEndpointKey = "argus_custom_endpoint"
AuthorizationCustomEndpointKey = "authorization_custom_endpoint"

@@ -60,2 +61,3 @@ DNSCustomEndpointKey = "dns_custom_endpoint"

ResourceManagerEndpointKey,
ArgusCustomEndpointKey,
AuthorizationCustomEndpointKey,

@@ -133,2 +135,3 @@ MongoDBFlexCustomEndpointKey,

viper.SetDefault(DNSCustomEndpointKey, "")
viper.SetDefault(ArgusCustomEndpointKey, "")
viper.SetDefault(AuthorizationCustomEndpointKey, "")

@@ -135,0 +138,0 @@ viper.SetDefault(MongoDBFlexCustomEndpointKey, "")

@@ -5,8 +5,12 @@ package utils

"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/google/uuid"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/google/uuid"
"github.com/stackitcloud/stackit-sdk-go/core/config"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"

@@ -18,2 +22,3 @@ )

testCredentialsGroupId = uuid.NewString()
testCredentialsId = "credentialsID" //nolint:gosec // linter false positive
)

@@ -23,2 +28,3 @@

testCredentialsGroupName = "testGroup"
testCredentialsName = "testCredential"
)

@@ -29,2 +35,3 @@

listCredentialsGroupsResp *objectstorage.ListCredentialsGroupsResponse
listAccessKeysReq objectstorage.ApiListAccessKeysRequest
}

@@ -38,2 +45,7 @@

}
func (m *objectStorageClientMocked) ListAccessKeys(_ context.Context, _ string) objectstorage.ApiListAccessKeysRequest {
return m.listAccessKeysReq
}
func TestGetCredentialsGroupName(t *testing.T) {

@@ -150,1 +162,140 @@ tests := []struct {

}
func TestGetCredentialsName(t *testing.T) {
tests := []struct {
description string
listAccessKeysResp *objectstorage.ListAccessKeysResponse
expectedOutput string
getCredentialsNameFails bool
isValid bool
}{
{
description: "base",
listAccessKeysResp: &objectstorage.ListAccessKeysResponse{
AccessKeys: &[]objectstorage.AccessKey{
{
KeyId: utils.Ptr(testCredentialsId),
DisplayName: utils.Ptr(testCredentialsName),
},
},
},
isValid: true,
expectedOutput: testCredentialsName,
},
{
description: "get credentials name fails",
getCredentialsNameFails: true,
isValid: false,
},
{
description: "multiple credentials",
listAccessKeysResp: &objectstorage.ListAccessKeysResponse{
AccessKeys: &[]objectstorage.AccessKey{
{
KeyId: utils.Ptr("test-UUID"),
DisplayName: utils.Ptr("test-name"),
},
{
KeyId: utils.Ptr(testCredentialsId),
DisplayName: utils.Ptr(testCredentialsName),
},
},
},
isValid: true,
expectedOutput: testCredentialsName,
},
{
description: "nil credentials",
listAccessKeysResp: &objectstorage.ListAccessKeysResponse{
AccessKeys: nil,
},
isValid: false,
},
{
description: "nil credentials id",
listAccessKeysResp: &objectstorage.ListAccessKeysResponse{
AccessKeys: &[]objectstorage.AccessKey{
{
KeyId: nil,
},
},
},
isValid: false,
},
{
description: "nil credentials name",
listAccessKeysResp: &objectstorage.ListAccessKeysResponse{
AccessKeys: &[]objectstorage.AccessKey{
{
KeyId: utils.Ptr(testCredentialsId),
DisplayName: nil,
},
},
},
isValid: false,
},
{
description: "empty credentials name",
listAccessKeysResp: &objectstorage.ListAccessKeysResponse{
AccessKeys: &[]objectstorage.AccessKey{
{
KeyId: utils.Ptr(testCredentialsId),
DisplayName: utils.Ptr(""),
},
},
},
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
mockedRespBytes, err := json.Marshal(tt.listAccessKeysResp)
if err != nil {
t.Fatalf("Failed to marshal mocked response: %v", err)
}
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
if tt.getCredentialsNameFails {
w.WriteHeader(http.StatusBadGateway)
w.Header().Set("Content-Type", "application/json")
_, err := w.Write([]byte("{\"message\": \"Something bad happened\""))
if err != nil {
t.Errorf("Failed to write bad response: %v", err)
}
return
}
_, err := w.Write(mockedRespBytes)
if err != nil {
t.Errorf("Failed to write response: %v", err)
}
})
mockedServer := httptest.NewServer(handler)
defer mockedServer.Close()
client, err := objectstorage.NewAPIClient(
config.WithEndpoint(mockedServer.URL),
config.WithoutAuthentication(),
)
if err != nil {
t.Fatalf("Failed to initialize client: %v", err)
}
output, err := GetCredentialsName(context.Background(), client, testProjectId, testCredentialsGroupId, testCredentialsId)
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)
}
})
}
}

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

ListCredentialsGroupsExecute(ctx context.Context, projectId string) (*objectstorage.ListCredentialsGroupsResponse, error)
ListAccessKeys(ctx context.Context, projectId string) objectstorage.ApiListAccessKeysRequest
}

@@ -34,1 +35,24 @@

}
func GetCredentialsName(ctx context.Context, apiClient ObjectStorageClient, projectId, credentialsGroupId, keyId string) (string, error) {
req := apiClient.ListAccessKeys(ctx, projectId)
req = req.CredentialsGroup(credentialsGroupId)
resp, err := req.Execute()
if err != nil {
return "", fmt.Errorf("list Object Storage credentials: %w", err)
}
credentials := resp.AccessKeys
if credentials == nil {
return "", fmt.Errorf("nil Object Storage credentials list")
}
for _, credential := range *credentials {
if credential.KeyId != nil && *credential.KeyId == keyId && credential.DisplayName != nil && *credential.DisplayName != "" {
return *credential.DisplayName, nil
}
}
return "", fmt.Errorf("could not find Object Storage credentials name")
}

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

testInstanceId = uuid.NewString()
testUserId = uuid.NewString()
)

@@ -22,2 +23,3 @@

testUserName = "user"
testDescription = "sample description"
)

@@ -28,2 +30,4 @@

getInstanceResp *secretsmanager.Instance
getUserFails bool
getUserResp *secretsmanager.User
}

@@ -38,2 +42,9 @@

func (s *secretsManagerClientMocked) GetUserExecute(_ context.Context, _, _, _ string) (*secretsmanager.User, error) {
if s.getUserFails {
return nil, fmt.Errorf("could not get user")
}
return s.getUserResp, nil
}
func TestGetInstanceName(t *testing.T) {

@@ -86,1 +97,81 @@ tests := []struct {

}
func TestGetUserDetails(t *testing.T) {
tests := []struct {
description string
getUserFails bool
GetUserResp *secretsmanager.User
isValid bool
expectedOutput string
}{
{
description: "base",
GetUserResp: &secretsmanager.User{
Username: utils.Ptr(testUserName),
Description: utils.Ptr(testDescription),
},
isValid: true,
expectedOutput: fmt.Sprintf("%q (%s)", testUserName, testDescription),
},
{
description: "user has no description",
GetUserResp: &secretsmanager.User{
Username: utils.Ptr(testUserName),
},
isValid: true,
expectedOutput: fmt.Sprintf("%q", testUserName),
},
{
description: "user has empty description",
GetUserResp: &secretsmanager.User{
Username: utils.Ptr(testUserName),
Description: utils.Ptr(""),
},
isValid: true,
expectedOutput: fmt.Sprintf("%q", testUserName),
},
{
description: "user has empty username",
GetUserResp: &secretsmanager.User{
Username: utils.Ptr(""),
},
isValid: false,
},
{
description: "user has no username",
GetUserResp: &secretsmanager.User{
Username: nil,
},
isValid: false,
},
{
description: "get user fails",
getUserFails: true,
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
client := &secretsManagerClientMocked{
getUserFails: tt.getUserFails,
getUserResp: tt.GetUserResp,
}
userLabel, err := GetUserLabel(context.Background(), client, testProjectId, testInstanceId, testUserId)
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 userLabel != tt.expectedOutput {
t.Errorf("expected user label to be %s, got %s", tt.expectedOutput, userLabel)
}
})
}
}

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

GetInstanceExecute(ctx context.Context, projectId, instanceId string) (*secretsmanager.Instance, error)
GetUserExecute(ctx context.Context, projectId string, instanceId string, userId string) (*secretsmanager.User, error)
}

@@ -22,1 +23,21 @@

}
func GetUserLabel(ctx context.Context, apiClient SecretsManagerClient, projectId, instanceId, userId string) (string, error) {
resp, err := apiClient.GetUserExecute(ctx, projectId, instanceId, userId)
if err != nil {
return "", fmt.Errorf("get Secrets Manager user: %w", err)
}
if resp.Username == nil || *resp.Username == "" {
// Should never happen, username is auto-generated
return "", fmt.Errorf("username is empty")
}
var userLabel string
if resp.Description == nil || *resp.Description == "" {
userLabel = fmt.Sprintf("%q", *resp.Username)
} else {
userLabel = fmt.Sprintf("%q (%s)", *resp.Username, *resp.Description)
}
return userLabel, nil
}