Skip to content

Commit 72a60d6

Browse files
author
Jan Sternagel
committed
beta kms cmd added
1 parent 5a7af4f commit 72a60d6

File tree

22 files changed

+2698
-0
lines changed

22 files changed

+2698
-0
lines changed
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
package create
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
8+
"github.com/goccy/go-yaml"
9+
"github.com/spf13/cobra"
10+
"github.com/stackitcloud/stackit-cli/internal/cmd/params"
11+
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
12+
cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors"
13+
"github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/client"
14+
15+
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
16+
"github.com/stackitcloud/stackit-cli/internal/pkg/flags"
17+
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
18+
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
19+
"github.com/stackitcloud/stackit-cli/internal/pkg/projectname"
20+
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
21+
"github.com/stackitcloud/stackit-sdk-go/services/kms"
22+
)
23+
24+
const (
25+
keyRingIdFlag = "key-ring"
26+
27+
algorithmFlag = "algorithm"
28+
backendFlag = "backend"
29+
descriptionFlag = "description"
30+
displayNameFlag = "name"
31+
importOnlyFlag = "import-only"
32+
purposeFlag = "purpose"
33+
)
34+
35+
type inputModel struct {
36+
*globalflags.GlobalFlagModel
37+
KeyRingId string
38+
39+
Algorithm *string
40+
Backend string // Keep "backend" as a variable, but set the default to "software" (see UI)
41+
Description *string
42+
Name *string
43+
ImportOnly *bool // Default false
44+
Purpose *string
45+
}
46+
47+
func NewCmd(params *params.CmdParams) *cobra.Command {
48+
cmd := &cobra.Command{
49+
Use: "create",
50+
Short: "Creates a KMS Key",
51+
Long: "Creates a KMS Key.",
52+
Args: args.NoArgs,
53+
Example: examples.Build(
54+
examples.NewExample(
55+
`Create a Symmetric KMS Key`,
56+
`$ stakit beta kms key create --key-ring "my-keyring-id" --algorithm "rsa_2048_oaep_sha256" --name "my-key-name" --purpose "symmetric_encrypt_decrypt"`),
57+
examples.NewExample(
58+
`Create a Message Authentication KMS Key`,
59+
`$ stakit beta kms key create --key-ring "my-keyring-id" --algorithm "hmac_sha512" --name "my-key-name" --purpose "message_authentication_code"`),
60+
),
61+
RunE: func(cmd *cobra.Command, _ []string) error {
62+
ctx := context.Background()
63+
model, err := parseInput(params.Printer, cmd)
64+
if err != nil {
65+
return err
66+
}
67+
68+
// Configure API client
69+
apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion)
70+
if err != nil {
71+
return err
72+
}
73+
74+
projectLabel, err := projectname.GetProjectName(ctx, params.Printer, params.CliVersion, cmd)
75+
if err != nil {
76+
params.Printer.Debug(print.ErrorLevel, "get project name: %v", err)
77+
projectLabel = model.ProjectId
78+
}
79+
80+
if !model.AssumeYes {
81+
prompt := fmt.Sprintf("Are you sure you want to create a KMS Key for project %q?", projectLabel)
82+
err = params.Printer.PromptForConfirmation(prompt)
83+
if err != nil {
84+
return err
85+
}
86+
}
87+
88+
// Call API
89+
req, _ := buildRequest(ctx, model, apiClient)
90+
if err != nil {
91+
return err
92+
}
93+
94+
key, err := req.Execute()
95+
if err != nil {
96+
return fmt.Errorf("create KMS Key: %w", err)
97+
}
98+
99+
// No wait exists for the key creation
100+
return outputResult(params.Printer, model.OutputFormat, projectLabel, key)
101+
},
102+
}
103+
configureFlags(cmd)
104+
return cmd
105+
}
106+
107+
func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) {
108+
globalFlags := globalflags.Parse(p, cmd)
109+
if globalFlags.ProjectId == "" {
110+
return nil, &cliErr.ProjectIdError{}
111+
}
112+
113+
// What checks could this need?
114+
// I would rather let the creation fail instead of checking all possible algorithms
115+
model := inputModel{
116+
GlobalFlagModel: globalFlags,
117+
KeyRingId: flags.FlagToStringValue(p, cmd, keyRingIdFlag),
118+
Algorithm: flags.FlagToStringPointer(p, cmd, algorithmFlag),
119+
Backend: flags.FlagWithDefaultToStringValue(p, cmd, backendFlag),
120+
Name: flags.FlagToStringPointer(p, cmd, displayNameFlag),
121+
Description: flags.FlagToStringPointer(p, cmd, descriptionFlag),
122+
ImportOnly: flags.FlagToBoolPointer(p, cmd, importOnlyFlag),
123+
Purpose: flags.FlagToStringPointer(p, cmd, purposeFlag),
124+
}
125+
126+
if p.IsVerbosityDebug() {
127+
modelStr, err := print.BuildDebugStrFromInputModel(model)
128+
if err != nil {
129+
p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err)
130+
} else {
131+
p.Debug(print.ErrorLevel, "parsed input values: %s", modelStr)
132+
}
133+
}
134+
135+
return &model, nil
136+
}
137+
138+
type kmsKeyClient interface {
139+
CreateKey(ctx context.Context, projectId string, regionId string, keyRingId string) kms.ApiCreateKeyRequest
140+
}
141+
142+
func buildRequest(ctx context.Context, model *inputModel, apiClient kmsKeyClient) (kms.ApiCreateKeyRequest, error) {
143+
req := apiClient.CreateKey(ctx, model.ProjectId, model.Region, model.KeyRingId)
144+
145+
// Question: Should there be additional checks here?
146+
req = req.CreateKeyPayload(kms.CreateKeyPayload{
147+
DisplayName: model.Name,
148+
Description: model.Description,
149+
Algorithm: kms.CreateKeyPayloadGetAlgorithmAttributeType(model.Algorithm),
150+
Backend: kms.CreateKeyPayloadGetBackendAttributeType(&model.Backend),
151+
Purpose: kms.CreateKeyPayloadGetPurposeAttributeType(model.Purpose),
152+
ImportOnly: model.ImportOnly,
153+
})
154+
return req, nil
155+
}
156+
157+
func outputResult(p *print.Printer, outputFormat, projectLabel string, resp *kms.Key) error {
158+
if resp == nil {
159+
return fmt.Errorf("response is nil")
160+
}
161+
162+
switch outputFormat {
163+
case print.JSONOutputFormat:
164+
details, err := json.MarshalIndent(resp, "", " ")
165+
if err != nil {
166+
return fmt.Errorf("marshal KMS Key: %w", err)
167+
}
168+
p.Outputln(string(details))
169+
return nil
170+
171+
case print.YAMLOutputFormat:
172+
details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true), yaml.UseJSONMarshaler())
173+
if err != nil {
174+
return fmt.Errorf("marshal KMS Key: %w", err)
175+
}
176+
p.Outputln(string(details))
177+
return nil
178+
179+
default:
180+
p.Outputf("Created Key for project %q. Key ID: %s\n", projectLabel, utils.PtrString(resp.Id))
181+
return nil
182+
}
183+
}
184+
185+
func configureFlags(cmd *cobra.Command) {
186+
cmd.Flags().Var(flags.UUIDFlag(), keyRingIdFlag, "ID of the KMS Key Ring")
187+
cmd.Flags().String(algorithmFlag, "", "En-/Decryption / signing algorithm")
188+
cmd.Flags().String(backendFlag, "software", "The backend that is responsible for maintaining this key")
189+
cmd.Flags().String(displayNameFlag, "", "The display name to distinguish multiple keys")
190+
cmd.Flags().String(descriptionFlag, "", "Optinal description of the Key")
191+
cmd.Flags().Bool(importOnlyFlag, false, "States whether versions can be created or only imported")
192+
cmd.Flags().String(purposeFlag, "", "Purpose of the Key. Enum: 'symmetric_encrypt_decrypt', 'asymmetric_encrypt_decrypt', 'message_authentication_code', 'asymmetric_sign_verify' ")
193+
194+
err := flags.MarkFlagsRequired(cmd, keyRingIdFlag, algorithmFlag, purposeFlag, displayNameFlag)
195+
cobra.CheckErr(err)
196+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package delete
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/spf13/cobra"
8+
"github.com/stackitcloud/stackit-cli/internal/cmd/params"
9+
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
10+
"github.com/stackitcloud/stackit-cli/internal/pkg/errors"
11+
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
12+
"github.com/stackitcloud/stackit-cli/internal/pkg/flags"
13+
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
14+
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
15+
kmsUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/utils"
16+
17+
"github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/client"
18+
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
19+
"github.com/stackitcloud/stackit-sdk-go/services/kms"
20+
)
21+
22+
const (
23+
keyRingIdFlag = "key-ring"
24+
keyIdFlag = "key"
25+
)
26+
27+
type inputModel struct {
28+
*globalflags.GlobalFlagModel
29+
KeyRingId string
30+
KeyId string
31+
}
32+
33+
func NewCmd(params *params.CmdParams) *cobra.Command {
34+
cmd := &cobra.Command{
35+
Use: "delete",
36+
Short: "Deletes a KMS Key",
37+
Long: "Deletes a KMS Key inside a specific Key Ring.",
38+
Args: args.NoArgs,
39+
Example: examples.Build(
40+
examples.NewExample(
41+
`Delete a KMS Key "my-key-id" inside the Key Ring "my-key-ring-id"`,
42+
`$ stackit beta kms keyring delete --key-ring "my-key-ring-id" --key "my-key-id"`),
43+
),
44+
RunE: func(cmd *cobra.Command, _ []string) error {
45+
ctx := context.Background()
46+
model, err := parseInput(params.Printer, cmd)
47+
if err != nil {
48+
return err
49+
}
50+
51+
// Configure API client
52+
apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion)
53+
if err != nil {
54+
return err
55+
}
56+
57+
keyName, err := kmsUtils.GetKeyName(ctx, apiClient, model.ProjectId, model.Region, model.KeyRingId, model.KeyId)
58+
if err != nil {
59+
params.Printer.Debug(print.ErrorLevel, "get key name: %v", err)
60+
keyName = model.KeyId
61+
}
62+
63+
if !model.AssumeYes {
64+
prompt := fmt.Sprintf("Are you sure you want to delete key %q? (This cannot be undone)", keyName)
65+
err = params.Printer.PromptForConfirmation(prompt)
66+
if err != nil {
67+
return err
68+
}
69+
}
70+
71+
// Call API
72+
req := buildRequest(ctx, model, apiClient)
73+
err = req.Execute()
74+
if err != nil {
75+
return fmt.Errorf("delete KMS Key: %w", err)
76+
}
77+
78+
// Don't wait for a month until the deletion was performed.
79+
// Just print the deletion date.
80+
deletionDate, err := kmsUtils.GetKeyDeletionDate(ctx, apiClient, model.ProjectId, model.Region, model.KeyRingId, model.KeyId)
81+
if err != nil {
82+
return err
83+
}
84+
85+
params.Printer.Info("Deletion of KMS Key %q scheduled successfully for the deletion date: %q\n", keyName, deletionDate)
86+
return nil
87+
},
88+
}
89+
90+
configureFlags(cmd)
91+
return cmd
92+
}
93+
94+
func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) {
95+
globalFlags := globalflags.Parse(p, cmd)
96+
if globalFlags.ProjectId == "" {
97+
return nil, &errors.ProjectIdError{}
98+
}
99+
100+
keyRingId := flags.FlagToStringValue(p, cmd, keyRingIdFlag)
101+
keyId := flags.FlagToStringValue(p, cmd, keyIdFlag)
102+
103+
// Validate the uuid format of the IDs
104+
errKeyRing := utils.ValidateUUID(keyRingId)
105+
errKey := utils.ValidateUUID(keyId)
106+
if errKeyRing != nil || errKey != nil {
107+
return nil, &errors.DSAInputPlanError{
108+
Cmd: cmd,
109+
}
110+
}
111+
112+
model := inputModel{
113+
GlobalFlagModel: globalFlags,
114+
KeyRingId: keyRingId,
115+
KeyId: keyId,
116+
}
117+
118+
if p.IsVerbosityDebug() {
119+
modelStr, err := print.BuildDebugStrFromInputModel(model)
120+
if err != nil {
121+
p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err)
122+
} else {
123+
p.Debug(print.DebugLevel, "parsed input values: %s", modelStr)
124+
}
125+
}
126+
127+
return &model, nil
128+
}
129+
130+
func buildRequest(ctx context.Context, model *inputModel, apiClient *kms.APIClient) kms.ApiDeleteKeyRequest {
131+
req := apiClient.DeleteKey(ctx, model.ProjectId, model.Region, model.KeyRingId, model.KeyId)
132+
return req
133+
}
134+
135+
func configureFlags(cmd *cobra.Command) {
136+
cmd.Flags().Var(flags.UUIDFlag(), keyRingIdFlag, "ID of the KMS Key Ring where the Key is stored")
137+
cmd.Flags().Var(flags.UUIDFlag(), keyIdFlag, "ID of the actual Key")
138+
err := flags.MarkFlagsRequired(cmd, keyRingIdFlag, keyIdFlag)
139+
cobra.CheckErr(err)
140+
}

0 commit comments

Comments
 (0)