diff --git a/cmd/kms/crypto/crypto.go b/cmd/kms/crypto/crypto.go new file mode 100644 index 000000000..55275920a --- /dev/null +++ b/cmd/kms/crypto/crypto.go @@ -0,0 +1,15 @@ +package crypto + +import ( + "github.com/exoscale/cli/cmd/kms" + "github.com/spf13/cobra" +) + +var cryptoCmd = &cobra.Command{ + Use: "crypto", + Short: "KMS key cryptographic operations", +} + +func init() { + kms.KMSCmd.AddCommand(cryptoCmd) +} diff --git a/cmd/kms/crypto/crypto_decrypt.go b/cmd/kms/crypto/crypto_decrypt.go new file mode 100644 index 000000000..90f7f44f2 --- /dev/null +++ b/cmd/kms/crypto/crypto_decrypt.go @@ -0,0 +1,97 @@ +package crypto + +import ( + "encoding/base64" + "os" + + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + "github.com/exoscale/cli/pkg/output" + "github.com/exoscale/cli/table" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type cryptoDecryptOutput struct { + Plaintext string `json:"plaintext"` +} + +func (o *cryptoDecryptOutput) ToJSON() { output.JSON(o) } +func (o *cryptoDecryptOutput) ToText() { output.Text(o) } +func (o *cryptoDecryptOutput) ToTable() { + t := table.NewTable(os.Stdout) + defer t.Render() + + t.SetHeader([]string{ + "PLAINTEXT", + }) + + t.Append([]string{ + o.Plaintext, + }) +} + +type cryptoDecryptCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"decrypt"` + + Key string `cli-arg:"#" cli-usage:"ID"` + Ciphertext string `cli-arg:"#" cli-usage:"CIPHERTEXT"` + + EncryptionContext string `cli-short:"e" cli-flag:"encryption-context" cli-usage:"encryption context to use for decryption"` + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"crypto zone"` +} + +func (c *cryptoDecryptCmd) CmdAliases() []string { return nil } + +func (c *cryptoDecryptCmd) CmdShort() string { + return "Decrypt a ciphertext." +} + +func (c *cryptoDecryptCmd) CmdLong() string { + return "Decrypt a ciphertext." +} + +func (c *cryptoDecryptCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *cryptoDecryptCmd) CmdRun(_ *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + ec := []byte(c.EncryptionContext) + decoded, err := base64.StdEncoding.DecodeString(c.Ciphertext) + if err != nil { + return err + } + req := v3.DecryptRequest{ + Ciphertext: decoded, + EncryptionContext: &ec, + } + + resp, err := client.Decrypt(ctx, v3.UUID(c.Key), req) + if err != nil { + return err + } + + if !globalstate.Quiet { + out := cryptoDecryptOutput{ + Plaintext: base64.StdEncoding.EncodeToString(resp.Plaintext), + } + return c.OutputFunc(&out, nil) + } + + return nil +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(cryptoCmd, &cryptoDecryptCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/crypto/crypto_encrypt.go b/cmd/kms/crypto/crypto_encrypt.go new file mode 100644 index 000000000..6e2493fd9 --- /dev/null +++ b/cmd/kms/crypto/crypto_encrypt.go @@ -0,0 +1,93 @@ +package crypto + +import ( + "encoding/base64" + "os" + + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + "github.com/exoscale/cli/pkg/output" + "github.com/exoscale/cli/table" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type cryptoEncryptOutput struct { + Ciphertext string `json:"ciphertext"` +} + +func (o *cryptoEncryptOutput) ToJSON() { output.JSON(o) } +func (o *cryptoEncryptOutput) ToText() { output.Text(o) } +func (o *cryptoEncryptOutput) ToTable() { + t := table.NewTable(os.Stdout) + defer t.Render() + + t.SetHeader([]string{ + "CIPHERTEXT", + }) + + t.Append([]string{ + o.Ciphertext, + }) +} + +type cryptoEncryptCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"encrypt"` + + Key string `cli-arg:"#" cli-usage:"ID"` + Plaintext string `cli-arg:"#" cli-usage:"PLAINTEXT"` + + EncryptionContext string `cli-short:"e" cli-flag:"encryption-context" cli-usage:"encryption context to use for encryption"` + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *cryptoEncryptCmd) CmdAliases() []string { return nil } + +func (c *cryptoEncryptCmd) CmdShort() string { + return "Encrypt a plaintext." +} + +func (c *cryptoEncryptCmd) CmdLong() string { + return "Encrypt a plaintext." +} + +func (c *cryptoEncryptCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *cryptoEncryptCmd) CmdRun(_ *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + ec := []byte(c.EncryptionContext) + req := v3.EncryptRequest{ + Plaintext: []byte(c.Plaintext), + EncryptionContext: &ec, + } + + resp, err := client.Encrypt(ctx, v3.UUID(c.Key), req) + if err != nil { + return err + } + + if !globalstate.Quiet { + out := cryptoEncryptOutput{ + Ciphertext: base64.StdEncoding.EncodeToString(resp.Ciphertext), + } + return c.OutputFunc(&out, nil) + } + + return nil +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(cryptoCmd, &cryptoEncryptCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/crypto/crypto_generate_data_key.go b/cmd/kms/crypto/crypto_generate_data_key.go new file mode 100644 index 000000000..1d230ee30 --- /dev/null +++ b/cmd/kms/crypto/crypto_generate_data_key.go @@ -0,0 +1,109 @@ +package crypto + +import ( + "encoding/base64" + "os" + "strconv" + + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + "github.com/exoscale/cli/pkg/output" + "github.com/exoscale/cli/table" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type cryptoGenerateDataKeyOutput struct { + Plaintext string `json:"plaintext"` + Ciphertext string `json:"ciphertext"` +} + +func (o *cryptoGenerateDataKeyOutput) ToJSON() { output.JSON(o) } +func (o *cryptoGenerateDataKeyOutput) ToText() { output.Text(o) } +func (o *cryptoGenerateDataKeyOutput) ToTable() { + t := table.NewTable(os.Stdout) + defer t.Render() + + t.SetHeader([]string{ + "PLAINTEXT", + "CIPHERTEXT", + }) + + t.Append([]string{ + o.Plaintext, + o.Ciphertext, + }) +} + +type cryptoGenerateDataKeyCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"generate-data-key"` + + Key string `cli-arg:"#" cli-usage:"ID"` + + KeySpec v3.GenerateDataKeyRequestKeySpec `cli-short:"s" cli-flag:"key-spec" cli-usage:"key spec for DEK [AES-256]"` + BytesCount string `cli-short:"b" cli-flag:"bytes-count" cli-usage:"number of bytes for DEK (1 - 1024)"` + EncryptionContext string `cli-short:"e" cli-flag:"encryption-context" cli-usage:"encryption context to use for DEK generation"` + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *cryptoGenerateDataKeyCmd) CmdAliases() []string { return nil } + +func (c *cryptoGenerateDataKeyCmd) CmdShort() string { + return "Generate a Data Encryption Key from a given KMS Key." +} + +func (c *cryptoGenerateDataKeyCmd) CmdLong() string { + return "Generate a Data Encryption Key from a given KMS Key." +} + +func (c *cryptoGenerateDataKeyCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *cryptoGenerateDataKeyCmd) CmdRun(_ *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + ec := []byte(c.EncryptionContext) + + var bytecount int + if c.BytesCount != "" { + n, err := strconv.Atoi(c.BytesCount) + if err != nil { + return err + } + bytecount = n + } + + req := v3.GenerateDataKeyRequest{ + KeySpec: c.KeySpec, + BytesCount: bytecount, + EncryptionContext: &ec, + } + + resp, err := client.GenerateDataKey(ctx, v3.UUID(c.Key), req) + if err != nil { + return err + } + + if !globalstate.Quiet { + out := cryptoGenerateDataKeyOutput{ + Ciphertext: base64.StdEncoding.EncodeToString(resp.Ciphertext), + Plaintext: base64.StdEncoding.EncodeToString(resp.Plaintext), + } + return c.OutputFunc(&out, nil) + } + return nil +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(cryptoCmd, &cryptoGenerateDataKeyCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/crypto/crypto_reencrypt.go b/cmd/kms/crypto/crypto_reencrypt.go new file mode 100644 index 000000000..7df7e2ad2 --- /dev/null +++ b/cmd/kms/crypto/crypto_reencrypt.go @@ -0,0 +1,96 @@ +package crypto + +import ( + "encoding/base64" + + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type cryptoReencryptCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"reencrypt"` + + Key string `cli-arg:"#" cli-usage:"SRC_ID"` + DestinationKey string `cli-arg:"#" cli-usage:"DEST_ID"` + Ciphertext string `cli-arg:"#" cli-usage:"CIPHERTEXT"` + + SourceEncryptionContext string `cli-short:"s" cli-flag:"source-encryption-context" cli-usage:"encryption context to use for source ciphertext decryption"` + DestEncryptionContext string `cli-short:"d" cli-flag:"dest-encryption-context" cli-usage:"encryption context to use for destination ciphertext encryption"` + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *cryptoReencryptCmd) CmdAliases() []string { return nil } + +func (c *cryptoReencryptCmd) CmdShort() string { + return "Decrypts and encrypts an exisiting ciphertext with newest key material or a different KMS key." +} + +func (c *cryptoReencryptCmd) CmdLong() string { + return "Decrypts an existing ciphertext using its original key material and re-encrypts the underlying plaintext using a specified KMS key or the latest key material of the same KMS Key." +} + +func (c *cryptoReencryptCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *cryptoReencryptCmd) CmdRun(_ *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + var sourceEC []byte + if c.SourceEncryptionContext != "" { + sourceEC = []byte(c.SourceEncryptionContext) + } + + decodedCipher, err := base64.StdEncoding.DecodeString(c.Ciphertext) + if err != nil { + return err + } + source := &v3.ReEncryptRequestSource{ + Ciphertext: decodedCipher, + EncryptionContext: &sourceEC, + Key: v3.UUID(c.Key), + } + + var destEC []byte + if c.DestEncryptionContext != "" { + destEC = []byte(c.DestEncryptionContext) + } + dest := &v3.ReEncryptRequestDestination{ + Key: v3.UUID(c.DestinationKey), + EncryptionContext: &destEC, + } + + req := v3.ReEncryptRequest{ + Source: source, + Destination: dest, + } + + resp, err := client.ReEncrypt(ctx, v3.UUID(c.Key), req) + if err != nil { + return err + } + + if !globalstate.Quiet { + out := cryptoEncryptOutput{ + Ciphertext: base64.StdEncoding.EncodeToString(resp.Ciphertext), + } + return c.OutputFunc(&out, nil) + } + + return nil +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(cryptoCmd, &cryptoReencryptCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/key/formatting.go b/cmd/kms/key/formatting.go new file mode 100644 index 000000000..348745867 --- /dev/null +++ b/cmd/kms/key/formatting.go @@ -0,0 +1,61 @@ +package key + +import ( + "fmt" + "os" + "strconv" + "strings" + + "github.com/exoscale/cli/pkg/output" + "github.com/exoscale/cli/table" + v3 "github.com/exoscale/egoscale/v3" +) + +type successResponseOutput v3.SuccessResponse + +func (o *successResponseOutput) ToJSON() { output.JSON(o) } +func (o *successResponseOutput) ToText() { output.Text(o) } +func (o *successResponseOutput) ToTable() { + t := table.NewTable(os.Stdout) + defer t.Render() + + t.SetHeader([]string{ + "STATUS", + }) + + t.Append([]string{ + string(o.Status), + }) +} + +func formatKeyRotationConfig(s *v3.KeyRotationConfig) string { + if s == nil { + return "" + } + return fmt.Sprintf("auto: %s\ncount: %d\nnextAt: %s\nrotationPeriod: %d", + strconv.FormatBool(*s.Automatic), + s.ManualCount, + s.NextAT, + s.RotationPeriod) +} + +func formatKeyMaterial(s *v3.KeyMaterial) string { + if s == nil { + return "-" + } + return fmt.Sprintf("auto: %s\ncreatedAt: %s\nversion: %d", + strconv.FormatBool(*s.Automatic), + s.CreatedAT, + s.Version) +} + +func formatReplicaStatus(s []v3.ReplicaState) string { + if len(s) == 0 { + return "-" + } + var res []string + for _, r := range s { + res = append(res, r.Zone) + } + return strings.Join(res, ", ") +} diff --git a/cmd/kms/key/key.go b/cmd/kms/key/key.go new file mode 100644 index 000000000..d1f7969a6 --- /dev/null +++ b/cmd/kms/key/key.go @@ -0,0 +1,15 @@ +package key + +import ( + "github.com/exoscale/cli/cmd/kms" + "github.com/spf13/cobra" +) + +var keyCmd = &cobra.Command{ + Use: "key", + Short: "KMS key", +} + +func init() { + kms.KMSCmd.AddCommand(keyCmd) +} diff --git a/cmd/kms/key/key_cancel_deletion.go b/cmd/kms/key/key_cancel_deletion.go new file mode 100644 index 000000000..3e512f1a7 --- /dev/null +++ b/cmd/kms/key/key_cancel_deletion.go @@ -0,0 +1,61 @@ +package key + +import ( + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type keyCancelDeletionCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"cancel-deletion"` + + Key string `cli-arg:"#" cli-usage:"ID"` + + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *keyCancelDeletionCmd) CmdAliases() []string { return nil } + +func (c *keyCancelDeletionCmd) CmdShort() string { + return "Cancel the scheduled deletion of a KMS Key." +} + +func (c *keyCancelDeletionCmd) CmdLong() string { + return "Cancel the scheduled deletion of a KMS Key." +} + +func (c *keyCancelDeletionCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *keyCancelDeletionCmd) CmdRun(_ *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + _, err = client.CancelKmsKeyDeletion(ctx, v3.UUID(c.Key)) + if err != nil { + return err + } + + if !globalstate.Quiet { + return (&KeyShowCmd{ + CliCommandSettings: c.CliCommandSettings, + Key: c.Key, + }).CmdRun(nil, nil) + } + + return nil +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(keyCmd, &keyCancelDeletionCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/key/key_create.go b/cmd/kms/key/key_create.go new file mode 100644 index 000000000..b2f6af2cb --- /dev/null +++ b/cmd/kms/key/key_create.go @@ -0,0 +1,80 @@ +package key + +import ( + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type keyCreateCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"create"` + + Name string `cli-arg:"#" cli-usage:"NAME"` + + Description string `cli-short:"d" cli-flag:"description" cli-usage:"key description"` + Usage string `cli-short:"u" cli-flag:"usage" cli-usage:"key usage [encrypt-decrypt]"` + Multizone bool `cli-short:"m" cli-flag:"multizone" cli-usage:"allow replication accross zones (default: false)"` + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *keyCreateCmd) CmdAliases() []string { return nil } + +func (c *keyCreateCmd) CmdShort() string { + return "Create a KMS Key in a given zone with a given name." +} + +func (c *keyCreateCmd) CmdLong() string { + return "Create a KMS Key in a given zone with a given name." +} + +func (c *keyCreateCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *keyCreateCmd) CmdRun(cmd *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + req := v3.CreateKmsKeyRequest{ + Name: c.Name, + } + + if cmd.Flags().Changed("usage") { + req.Usage = v3.CreateKmsKeyRequestUsage(c.Usage) + } + + if cmd.Flags().Changed("description") { + req.Description = c.Description + } + + if cmd.Flags().Changed("multizone") { + req.MultiZone = &c.Multizone + } + + resp, err := client.CreateKmsKey(ctx, req) + if err != nil { + return err + } + + if !globalstate.Quiet { + return (&KeyShowCmd{ + CliCommandSettings: c.CliCommandSettings, + Key: resp.ID.String(), + }).CmdRun(nil, nil) + } + + return nil +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(keyCmd, &keyCreateCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/key/key_disable.go b/cmd/kms/key/key_disable.go new file mode 100644 index 000000000..edce33b55 --- /dev/null +++ b/cmd/kms/key/key_disable.go @@ -0,0 +1,60 @@ +package key + +import ( + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type keyDisableCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"disable"` + + Key string `cli-arg:"#" cli-usage:"ID"` + + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *keyDisableCmd) CmdAliases() []string { return nil } + +func (c *keyDisableCmd) CmdShort() string { + return "Disables a KMS Key." +} + +func (c *keyDisableCmd) CmdLong() string { + return "Disables a KMS Key." +} + +func (c *keyDisableCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *keyDisableCmd) CmdRun(_ *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + if _, err := client.DisableKmsKey(ctx, v3.UUID(c.Key)); err != nil { + return err + } + + if !globalstate.Quiet { + return (&KeyShowCmd{ + CliCommandSettings: c.CliCommandSettings, + Key: c.Key, + }).CmdRun(nil, nil) + } + + return nil +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(keyCmd, &keyDisableCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/key/key_enable.go b/cmd/kms/key/key_enable.go new file mode 100644 index 000000000..9d4fed4e8 --- /dev/null +++ b/cmd/kms/key/key_enable.go @@ -0,0 +1,60 @@ +package key + +import ( + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type keyEnableCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"enable"` + + Key string `cli-arg:"#" cli-usage:"ID"` + + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *keyEnableCmd) CmdAliases() []string { return nil } + +func (c *keyEnableCmd) CmdShort() string { + return "Enables a KMS Key." +} + +func (c *keyEnableCmd) CmdLong() string { + return "Enables a KMS Key." +} + +func (c *keyEnableCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *keyEnableCmd) CmdRun(_ *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + if _, err := client.EnableKmsKey(ctx, v3.UUID(c.Key)); err != nil { + return err + } + + if !globalstate.Quiet { + return (&KeyShowCmd{ + CliCommandSettings: c.CliCommandSettings, + Key: c.Key, + }).CmdRun(nil, nil) + } + + return nil +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(keyCmd, &keyEnableCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/key/key_list.go b/cmd/kms/key/key_list.go new file mode 100644 index 000000000..2b92c35de --- /dev/null +++ b/cmd/kms/key/key_list.go @@ -0,0 +1,104 @@ +package key + +import ( + "os" + "strconv" + "strings" + + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + "github.com/exoscale/cli/pkg/output" + "github.com/exoscale/cli/table" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type keyListOutput struct { + v3.ListKmsKeysResponse +} + +func (o *keyListOutput) ToJSON() { output.JSON(o) } +func (o *keyListOutput) ToText() { output.Text(o) } +func (o *keyListOutput) ToTable() { + t := table.NewTable(os.Stdout) + defer t.Render() + + t.SetHeader([]string{ + "ID", + "NAME", + "ORIGINZONE", + "STATUS", + "MULTIZONE", + "REPLICAS", + }) + + for _, key := range o.KmsKeys { + t.Append([]string{ + string(key.ID), + key.Name, + string(key.OriginZone), + string(key.Status), + strconv.FormatBool(*key.MultiZone), + strings.Join(key.Replicas, ", "), + }) + } +} + +type keyListCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"list"` + + IgnoreReplica bool `cli-short:"i" cli-flag:"ignore-replica" cli-usage:"filter out replicas"` + Status string `cli-short:"s" cli-flag:"status" cli-usage:"filter by key status [enabled|disabled|pending-deletion]"` + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *keyListCmd) CmdAliases() []string { return exocmd.GListAlias } + +func (c *keyListCmd) CmdShort() string { + return "List KMS Keys details for an organization in a given zone." +} + +func (c *keyListCmd) CmdLong() string { + return "List KMS Keys details for an organization in a given zone." +} + +func (c *keyListCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *keyListCmd) CmdRun(_ *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + keys, err := client.ListKmsKeys(ctx) + if err != nil { + return err + } + + filtered := make([]v3.ListKmsKeysResponseEntry, 0, len(keys.KmsKeys)) + for _, key := range keys.KmsKeys { + if c.IgnoreReplica && key.OriginZone != string(c.Zone) { + continue + } + if c.Status != "" && string(key.Status) != c.Status { + continue + } + filtered = append(filtered, key) + } + + out := keyListOutput{v3.ListKmsKeysResponse{KmsKeys: filtered}} + + return c.OutputFunc(&out, nil) +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(keyCmd, &keyListCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/key/key_replicate.go b/cmd/kms/key/key_replicate.go new file mode 100644 index 000000000..79e8e4e19 --- /dev/null +++ b/cmd/kms/key/key_replicate.go @@ -0,0 +1,65 @@ +package key + +import ( + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type keyReplicateCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"replicate"` + + Key string `cli-arg:"#" cli-usage:"ID"` + TargetZone v3.ZoneName `cli-arg:"#" cli-usage:"TARGET_ZONE"` + + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *keyReplicateCmd) CmdAliases() []string { return nil } + +func (c *keyReplicateCmd) CmdShort() string { + return "Replicate a KMS key to a target zone." +} + +func (c *keyReplicateCmd) CmdLong() string { + return "Replicate a KMS key to a target zone." +} + +func (c *keyReplicateCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *keyReplicateCmd) CmdRun(_ *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + req := v3.ReplicateKmsKeyRequest{ + Zone: string(c.TargetZone), + } + + resp, err := client.ReplicateKmsKey(ctx, v3.UUID(c.Key), req) + if err != nil { + return err + } + + if !globalstate.Quiet { + out := successResponseOutput{ + Status: resp.Status, + } + return c.OutputFunc(&out, nil) + } + return nil +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(keyCmd, &keyReplicateCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/key/key_rotate.go b/cmd/kms/key/key_rotate.go new file mode 100644 index 000000000..509bcb0ea --- /dev/null +++ b/cmd/kms/key/key_rotate.go @@ -0,0 +1,61 @@ +package key + +import ( + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type keyRotateCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"rotate"` + + Key string `cli-arg:"#" cli-usage:"ID"` + + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *keyRotateCmd) CmdAliases() []string { return nil } + +func (c *keyRotateCmd) CmdShort() string { + return "Perform a manual rotation of the key material for a symmetric key." +} + +func (c *keyRotateCmd) CmdLong() string { + return "Perform a manual rotation of the key material for a symmetric key." +} + +func (c *keyRotateCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *keyRotateCmd) CmdRun(_ *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + _, err = client.RotateKmsKey(ctx, v3.UUID(c.Key)) + if err != nil { + return err + } + + if !globalstate.Quiet { + return (&KeyShowCmd{ + CliCommandSettings: c.CliCommandSettings, + Key: c.Key, + }).CmdRun(nil, nil) + } + + return nil +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(keyCmd, &keyRotateCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/key/key_rotation_disable.go b/cmd/kms/key/key_rotation_disable.go new file mode 100644 index 000000000..8166bb4c0 --- /dev/null +++ b/cmd/kms/key/key_rotation_disable.go @@ -0,0 +1,60 @@ +package key + +import ( + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type keyRotationDisableCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"disable-rotation"` + + Key string `cli-arg:"#" cli-usage:"ID"` + + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *keyRotationDisableCmd) CmdAliases() []string { return nil } + +func (c *keyRotationDisableCmd) CmdShort() string { + return "Disable the periodic rotation of a KMS Key." +} + +func (c *keyRotationDisableCmd) CmdLong() string { + return "Disable the periodic rotation of a KMS Key." +} + +func (c *keyRotationDisableCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *keyRotationDisableCmd) CmdRun(_ *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + if _, err := client.DisableKmsKeyRotation(ctx, v3.UUID(c.Key)); err != nil { + return err + } + + if !globalstate.Quiet { + return (&KeyShowCmd{ + CliCommandSettings: c.CliCommandSettings, + Key: c.Key, + }).CmdRun(nil, nil) + } + + return nil +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(keyCmd, &keyRotationDisableCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/key/key_rotation_enable.go b/cmd/kms/key/key_rotation_enable.go new file mode 100644 index 000000000..012a4f97f --- /dev/null +++ b/cmd/kms/key/key_rotation_enable.go @@ -0,0 +1,72 @@ +package key + +import ( + "strconv" + + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type keyRotationEnableCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"enable-rotation"` + + Key string `cli-arg:"#" cli-usage:"ID"` + + RotationPeriod string `cli-flag:"rotation-period" cli-short:"r" cli-usage:"number of days for auto rotation period (90 - 2560, default: 365)"` + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *keyRotationEnableCmd) CmdAliases() []string { return nil } + +func (c *keyRotationEnableCmd) CmdShort() string { + return "Enable the periodic rotation of a KMS Key." +} + +func (c *keyRotationEnableCmd) CmdLong() string { + return "Enable the periodic rotation of a KMS Key." +} + +func (c *keyRotationEnableCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *keyRotationEnableCmd) CmdRun(cmd *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + var req v3.EnableKmsKeyRotationRequest + if cmd.Flags().Changed("rotation-period") { + n, err := strconv.Atoi(c.RotationPeriod) + if err != nil { + return err + } + req.RotationPeriod = n + } + + if _, err := client.EnableKmsKeyRotation(ctx, v3.UUID(c.Key), req); err != nil { + return err + } + + if !globalstate.Quiet { + return (&KeyShowCmd{ + CliCommandSettings: c.CliCommandSettings, + Key: c.Key, + }).CmdRun(nil, nil) + } + + return nil +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(keyCmd, &keyRotationEnableCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/key/key_rotation_list.go b/cmd/kms/key/key_rotation_list.go new file mode 100644 index 000000000..94621b5ad --- /dev/null +++ b/cmd/kms/key/key_rotation_list.go @@ -0,0 +1,86 @@ +package key + +import ( + "os" + "strconv" + + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + "github.com/exoscale/cli/pkg/output" + "github.com/exoscale/cli/table" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type keyRotationListOutput struct { + v3.ListKmsKeyRotationsResponse +} + +func (o *keyRotationListOutput) ToJSON() { output.JSON(o) } +func (o *keyRotationListOutput) ToText() { output.Text(o) } +func (o *keyRotationListOutput) ToTable() { + t := table.NewTable(os.Stdout) + defer t.Render() + + t.SetHeader([]string{ + "VERSION", + "ROTATED_AT", + "AUTOMATIC", + }) + + for _, rotation := range o.Rotations { + t.Append([]string{ + strconv.Itoa(rotation.Version), + rotation.RotatedAT.String(), + strconv.FormatBool(*rotation.Automatic), + }) + } +} + +type keyRotationListCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"list-rotation"` + + Key string `cli-arg:"#" cli-usage:"ID"` + + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *keyRotationListCmd) CmdAliases() []string { return exocmd.GListAlias } + +func (c *keyRotationListCmd) CmdShort() string { + return "List all the key material versions of a KMS Key." +} + +func (c *keyRotationListCmd) CmdLong() string { + return "List all the key material versions of a KMS Key." +} + +func (c *keyRotationListCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *keyRotationListCmd) CmdRun(_ *cobra.Command, _ []string) error { + ctx := exocmd.GContext + + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + resp, err := client.ListKmsKeyRotations(ctx, v3.UUID(c.Key)) + if err != nil { + return err + } + out := keyRotationListOutput{*resp} + + return c.OutputFunc(&out, nil) +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(keyCmd, &keyRotationListCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/key/key_schedule_deletion.go b/cmd/kms/key/key_schedule_deletion.go new file mode 100644 index 000000000..5bb9394e8 --- /dev/null +++ b/cmd/kms/key/key_schedule_deletion.go @@ -0,0 +1,73 @@ +package key + +import ( + "strconv" + + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type keyScheduleDeletionCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"schedule-deletion"` + + Key string `cli-arg:"#" cli-usage:"ID"` + + DelayDays string `cli-short:"d" cli-flag:"delay-days" cli-usage:"number of days before deletion (7 - 30, default: 30)"` + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *keyScheduleDeletionCmd) CmdAliases() []string { return nil } + +func (c *keyScheduleDeletionCmd) CmdShort() string { + return "Schedule a KMS key for deletion after a delay." +} + +func (c *keyScheduleDeletionCmd) CmdLong() string { + return "Schedule a KMS key for deletion after a delay." +} + +func (c *keyScheduleDeletionCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *keyScheduleDeletionCmd) CmdRun(cmd *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + var req v3.ScheduleKmsKeyDeletionRequest + if cmd.Flags().Changed("delay-days") { + n, err := strconv.Atoi(c.DelayDays) + if err != nil { + return err + } + req.DelayDays = n + } + + _, err = client.ScheduleKmsKeyDeletion(ctx, v3.UUID(c.Key), req) + if err != nil { + return err + } + + if !globalstate.Quiet { + return (&KeyShowCmd{ + CliCommandSettings: c.CliCommandSettings, + Key: c.Key, + }).CmdRun(nil, nil) + } + + return nil +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(keyCmd, &keyScheduleDeletionCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/key/key_show.go b/cmd/kms/key/key_show.go new file mode 100644 index 000000000..4755321e8 --- /dev/null +++ b/cmd/kms/key/key_show.go @@ -0,0 +1,91 @@ +package key + +import ( + "time" + + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + "github.com/exoscale/cli/pkg/output" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type KeyShowOutput struct { + ID v3.UUID `json:"id" validate:"required"` + Name string `json:"name" validate:"required"` + CreatedAt time.Time `json:"created-at" validate:"required"` + Multizone bool `json:"multi-zone" validate:"required"` + OriginZone string `json:"origin-zone" validate:"required"` + Status v3.GetKmsKeyResponseStatus `json:"status" validate:"required"` + ReplicasStatus string `json:"replicas-status,omitempty"` + Material string `json:"material" validate:"required"` + Rotation string `json:"rotation" validate:"required"` + Usage string `json:"usage" validate:"required"` + Source v3.GetKmsKeyResponseSource `json:"source" validate:"required"` + Description string `json:"description" validate:"required"` +} + +func (o *KeyShowOutput) Type() string { return "KMS key" } +func (o *KeyShowOutput) ToJSON() { output.JSON(o) } +func (o *KeyShowOutput) ToText() { output.Text(o) } +func (o *KeyShowOutput) ToTable() { output.Table(o) } + +type KeyShowCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"show"` + + Key string `cli-arg:"#" cli-usage:"ID"` + + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *KeyShowCmd) CmdAliases() []string { return exocmd.GShowAlias } + +func (c *KeyShowCmd) CmdShort() string { + return "Retrieve KMS Key details." +} + +func (c *KeyShowCmd) CmdLong() string { + return "Retrieve KMS Key details." +} + +func (c *KeyShowCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *KeyShowCmd) CmdRun(_ *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + resp, err := client.GetKmsKey(ctx, v3.UUID(c.Key)) + if err != nil { + return err + } + + out := KeyShowOutput{ + ID: resp.ID, + Name: resp.Name, + CreatedAt: resp.CreatedAT, + Multizone: *resp.MultiZone, + OriginZone: resp.OriginZone, + Status: resp.Status, + ReplicasStatus: formatReplicaStatus(resp.ReplicasStatus), + Material: formatKeyMaterial(resp.Material), + Rotation: formatKeyRotationConfig(resp.Rotation), + Usage: string(resp.Usage), + Source: resp.Source, + Description: resp.Description, + } + return c.OutputFunc(&out, nil) +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(keyCmd, &KeyShowCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/kms.go b/cmd/kms/kms.go new file mode 100644 index 000000000..67791de36 --- /dev/null +++ b/cmd/kms/kms.go @@ -0,0 +1,15 @@ +package kms + +import ( + exocmd "github.com/exoscale/cli/cmd" + "github.com/spf13/cobra" +) + +var KMSCmd = &cobra.Command{ + Use: "kms", + Short: "Key management", +} + +func init() { + exocmd.RootCmd.AddCommand(KMSCmd) +} diff --git a/cmd/root.go b/cmd/root.go index d1623e0ed..cc996ab7e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -116,7 +116,7 @@ func formatError(err error) string { return apiErr.Unwrap().Error() } - msg := apiErr.Unwrap().Error() + ": " + lead + msg := apiErr.Unwrap().Error() + ": " + apiErr.Title + ": " + lead for _, e := range apiErr.Errors { field := formatFieldName(e.Location) detail := formatDetail(e.Detail) diff --git a/cmd/subcommands/init.go b/cmd/subcommands/init.go index bdb53bdcd..d54545275 100644 --- a/cmd/subcommands/init.go +++ b/cmd/subcommands/init.go @@ -26,5 +26,8 @@ import ( _ "github.com/exoscale/cli/cmd/dbaas" _ "github.com/exoscale/cli/cmd/dns" _ "github.com/exoscale/cli/cmd/iam" + _ "github.com/exoscale/cli/cmd/kms" + _ "github.com/exoscale/cli/cmd/kms/crypto" + _ "github.com/exoscale/cli/cmd/kms/key" _ "github.com/exoscale/cli/cmd/storage" ) diff --git a/go.mod b/go.mod index bd99f2e61..47c4760f4 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/s3 v1.2.0 github.com/aws/smithy-go v1.1.0 github.com/dustin/go-humanize v1.0.1 - github.com/exoscale/egoscale/v3 v3.1.36-0.20260424083744-33446130ebd9 + github.com/exoscale/egoscale/v3 v3.1.38 github.com/exoscale/openapi-cli-generator v1.2.0 github.com/fatih/camelcase v1.0.0 github.com/hashicorp/go-multierror v1.1.1 diff --git a/go.sum b/go.sum index 7591ae72c..984c81abe 100644 --- a/go.sum +++ b/go.sum @@ -161,8 +161,8 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go. github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= -github.com/exoscale/egoscale/v3 v3.1.36-0.20260424083744-33446130ebd9 h1:3fQP3zqWwhhiWflStd1Ns1DPXx2yxpN0fhlqEZ/vYgI= -github.com/exoscale/egoscale/v3 v3.1.36-0.20260424083744-33446130ebd9/go.mod h1:/1RTNibUdltIdzBbFxMMewNAkB6KKdxzRE/Icu8K5RU= +github.com/exoscale/egoscale/v3 v3.1.38 h1:Xu/eZa8gGgBcS3eJ05PTOGR6/CvGy4I5z19LGyYyxOg= +github.com/exoscale/egoscale/v3 v3.1.38/go.mod h1:DUTgeubl5msPAo3SKFed04AxNhyTNOrCTJHZDRYLR10= github.com/exoscale/openapi-cli-generator v1.2.0 h1:xgTff1bInBP+JZCauD7Jq9GNBFoKK31Cnv5FIAcxtrk= github.com/exoscale/openapi-cli-generator v1.2.0/go.mod h1:TZBnbT7f3hJ5ImyUphJwRM+X5xF/zCQZ6o8a42gQeTs= github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= diff --git a/vendor/github.com/exoscale/egoscale/v3/operations.go b/vendor/github.com/exoscale/egoscale/v3/operations.go index 40661f6be..043c873bb 100644 --- a/vendor/github.com/exoscale/egoscale/v3/operations.go +++ b/vendor/github.com/exoscale/egoscale/v3/operations.go @@ -12,9 +12,9 @@ import ( "time" ) -// FindAIAPIKey attempts to find an AIAPIKey by nameOrID. -func (l ListAIAPIKeysResponse) FindAIAPIKey(nameOrID string) (AIAPIKey, error) { - var result []AIAPIKey +// FindListAIAPIKeysResponseEntry attempts to find an ListAIAPIKeysResponseEntry by nameOrID. +func (l ListAIAPIKeysResponse) FindListAIAPIKeysResponseEntry(nameOrID string) (ListAIAPIKeysResponseEntry, error) { + var result []ListAIAPIKeysResponseEntry for i, elem := range l.AIAPIKeys { if string(elem.Name) == nameOrID || string(elem.ID) == nameOrID { result = append(result, l.AIAPIKeys[i]) @@ -25,15 +25,15 @@ func (l ListAIAPIKeysResponse) FindAIAPIKey(nameOrID string) (AIAPIKey, error) { } if len(result) > 1 { - return AIAPIKey{}, fmt.Errorf("%q too many found in ListAIAPIKeysResponse: %w", nameOrID, ErrConflict) + return ListAIAPIKeysResponseEntry{}, fmt.Errorf("%q too many found in ListAIAPIKeysResponse: %w", nameOrID, ErrConflict) } - return AIAPIKey{}, fmt.Errorf("%q not found in ListAIAPIKeysResponse: %w", nameOrID, ErrNotFound) + return ListAIAPIKeysResponseEntry{}, fmt.Errorf("%q not found in ListAIAPIKeysResponse: %w", nameOrID, ErrNotFound) } // List AI API keys for an organization func (c Client) ListAIAPIKeys(ctx context.Context) (*ListAIAPIKeysResponse, error) { - path := "/ai/ai-api-key" + path := "/ai/api-key" request, err := http.NewRequestWithContext(ctx, "GET", c.serverEndpoint+path, nil) if err != nil { @@ -76,8 +76,8 @@ func (c Client) ListAIAPIKeys(ctx context.Context) (*ListAIAPIKeysResponse, erro } // Create a new AI API key -func (c Client) CreateAIAPIKey(ctx context.Context, req CreateAIAPIKeyRequest) (*AIAPIKeyWithValue, error) { - path := "/ai/ai-api-key" +func (c Client) CreateAIAPIKey(ctx context.Context, req CreateAIAPIKeyRequest) (*CreateAIAPIKeyResponse, error) { + path := "/ai/api-key" body, err := prepareJSONBody(req) if err != nil { @@ -118,7 +118,7 @@ func (c Client) CreateAIAPIKey(ctx context.Context, req CreateAIAPIKeyRequest) ( return nil, fmt.Errorf("CreateAIAPIKey: http response: %w", err) } - bodyresp := new(AIAPIKeyWithValue) + bodyresp := new(CreateAIAPIKeyResponse) if err := prepareJSONResponse(response, bodyresp); err != nil { return nil, fmt.Errorf("CreateAIAPIKey: prepare Json response: %w", err) } @@ -126,36 +126,32 @@ func (c Client) CreateAIAPIKey(ctx context.Context, req CreateAIAPIKeyRequest) ( return bodyresp, nil } -type DeleteAIAPIKeyResponse struct { - Deleted *bool `json:"deleted" validate:"required"` -} - -// Delete AI API key -func (c Client) DeleteAIAPIKey(ctx context.Context, id UUID) (*DeleteAIAPIKeyResponse, error) { - path := fmt.Sprintf("/ai/ai-api-key/%v", id) +// Get AI API key metadata +func (c Client) GetAIAPIKey(ctx context.Context, id UUID) (*GetAIAPIKeyResponse, error) { + path := fmt.Sprintf("/ai/api-key/%v", id) - request, err := http.NewRequestWithContext(ctx, "DELETE", c.serverEndpoint+path, nil) + request, err := http.NewRequestWithContext(ctx, "GET", c.serverEndpoint+path, nil) if err != nil { - return nil, fmt.Errorf("DeleteAIAPIKey: new request: %w", err) + return nil, fmt.Errorf("GetAIAPIKey: new request: %w", err) } request.Header.Add("User-Agent", c.getUserAgent()) if err := c.executeRequestInterceptors(ctx, request); err != nil { - return nil, fmt.Errorf("DeleteAIAPIKey: execute request editors: %w", err) + return nil, fmt.Errorf("GetAIAPIKey: execute request editors: %w", err) } if err := c.signRequest(request); err != nil { - return nil, fmt.Errorf("DeleteAIAPIKey: sign request: %w", err) + return nil, fmt.Errorf("GetAIAPIKey: sign request: %w", err) } if c.trace { - dumpRequest(request, "delete-ai-api-key") + dumpRequest(request, "get-ai-api-key") } response, err := c.httpClient.Do(request) if err != nil { - return nil, fmt.Errorf("DeleteAIAPIKey: http client do: %w", err) + return nil, fmt.Errorf("GetAIAPIKey: http client do: %w", err) } if c.trace { @@ -163,43 +159,50 @@ func (c Client) DeleteAIAPIKey(ctx context.Context, id UUID) (*DeleteAIAPIKeyRes } if err := handleHTTPErrorResp(response); err != nil { - return nil, fmt.Errorf("DeleteAIAPIKey: http response: %w", err) + return nil, fmt.Errorf("GetAIAPIKey: http response: %w", err) } - bodyresp := new(DeleteAIAPIKeyResponse) + bodyresp := new(GetAIAPIKeyResponse) if err := prepareJSONResponse(response, bodyresp); err != nil { - return nil, fmt.Errorf("DeleteAIAPIKey: prepare Json response: %w", err) + return nil, fmt.Errorf("GetAIAPIKey: prepare Json response: %w", err) } return bodyresp, nil } -// Get AI API key metadata -func (c Client) GetAIAPIKey(ctx context.Context, id UUID) (*AIAPIKey, error) { - path := fmt.Sprintf("/ai/ai-api-key/%v", id) +// Update AI API key name and/or scope +func (c Client) UpdateAIAPIKey(ctx context.Context, id UUID, req UpdateAIAPIKeyRequest) (*UpdateAIAPIKeyResponse, error) { + path := fmt.Sprintf("/ai/api-key/%v", id) - request, err := http.NewRequestWithContext(ctx, "GET", c.serverEndpoint+path, nil) + body, err := prepareJSONBody(req) if err != nil { - return nil, fmt.Errorf("GetAIAPIKey: new request: %w", err) + return nil, fmt.Errorf("UpdateAIAPIKey: prepare Json body: %w", err) + } + + request, err := http.NewRequestWithContext(ctx, "PATCH", c.serverEndpoint+path, body) + if err != nil { + return nil, fmt.Errorf("UpdateAIAPIKey: new request: %w", err) } request.Header.Add("User-Agent", c.getUserAgent()) + request.Header.Add("Content-Type", "application/json") + if err := c.executeRequestInterceptors(ctx, request); err != nil { - return nil, fmt.Errorf("GetAIAPIKey: execute request editors: %w", err) + return nil, fmt.Errorf("UpdateAIAPIKey: execute request editors: %w", err) } if err := c.signRequest(request); err != nil { - return nil, fmt.Errorf("GetAIAPIKey: sign request: %w", err) + return nil, fmt.Errorf("UpdateAIAPIKey: sign request: %w", err) } if c.trace { - dumpRequest(request, "get-ai-api-key") + dumpRequest(request, "update-ai-api-key") } response, err := c.httpClient.Do(request) if err != nil { - return nil, fmt.Errorf("GetAIAPIKey: http client do: %w", err) + return nil, fmt.Errorf("UpdateAIAPIKey: http client do: %w", err) } if c.trace { @@ -207,50 +210,43 @@ func (c Client) GetAIAPIKey(ctx context.Context, id UUID) (*AIAPIKey, error) { } if err := handleHTTPErrorResp(response); err != nil { - return nil, fmt.Errorf("GetAIAPIKey: http response: %w", err) + return nil, fmt.Errorf("UpdateAIAPIKey: http response: %w", err) } - bodyresp := new(AIAPIKey) + bodyresp := new(UpdateAIAPIKeyResponse) if err := prepareJSONResponse(response, bodyresp); err != nil { - return nil, fmt.Errorf("GetAIAPIKey: prepare Json response: %w", err) + return nil, fmt.Errorf("UpdateAIAPIKey: prepare Json response: %w", err) } return bodyresp, nil } -// Update AI API key name and/or scope -func (c Client) UpdateAIAPIKey(ctx context.Context, id UUID, req UpdateAIAPIKeyRequest) (*AIAPIKey, error) { - path := fmt.Sprintf("/ai/ai-api-key/%v", id) +// Reveal AI API key plaintext value +func (c Client) RevealAIAPIKey(ctx context.Context, id UUID) (*RevealAIAPIKeyResponse, error) { + path := fmt.Sprintf("/ai/api-key/%v/reveal", id) - body, err := prepareJSONBody(req) + request, err := http.NewRequestWithContext(ctx, "GET", c.serverEndpoint+path, nil) if err != nil { - return nil, fmt.Errorf("UpdateAIAPIKey: prepare Json body: %w", err) - } - - request, err := http.NewRequestWithContext(ctx, "PATCH", c.serverEndpoint+path, body) - if err != nil { - return nil, fmt.Errorf("UpdateAIAPIKey: new request: %w", err) + return nil, fmt.Errorf("RevealAIAPIKey: new request: %w", err) } request.Header.Add("User-Agent", c.getUserAgent()) - request.Header.Add("Content-Type", "application/json") - if err := c.executeRequestInterceptors(ctx, request); err != nil { - return nil, fmt.Errorf("UpdateAIAPIKey: execute request editors: %w", err) + return nil, fmt.Errorf("RevealAIAPIKey: execute request editors: %w", err) } if err := c.signRequest(request); err != nil { - return nil, fmt.Errorf("UpdateAIAPIKey: sign request: %w", err) + return nil, fmt.Errorf("RevealAIAPIKey: sign request: %w", err) } if c.trace { - dumpRequest(request, "update-ai-api-key") + dumpRequest(request, "reveal-ai-api-key") } response, err := c.httpClient.Do(request) if err != nil { - return nil, fmt.Errorf("UpdateAIAPIKey: http client do: %w", err) + return nil, fmt.Errorf("RevealAIAPIKey: http client do: %w", err) } if c.trace { @@ -258,20 +254,20 @@ func (c Client) UpdateAIAPIKey(ctx context.Context, id UUID, req UpdateAIAPIKeyR } if err := handleHTTPErrorResp(response); err != nil { - return nil, fmt.Errorf("UpdateAIAPIKey: http response: %w", err) + return nil, fmt.Errorf("RevealAIAPIKey: http response: %w", err) } - bodyresp := new(AIAPIKey) + bodyresp := new(RevealAIAPIKeyResponse) if err := prepareJSONResponse(response, bodyresp); err != nil { - return nil, fmt.Errorf("UpdateAIAPIKey: prepare Json response: %w", err) + return nil, fmt.Errorf("RevealAIAPIKey: prepare Json response: %w", err) } return bodyresp, nil } // Rotate AI API key value -func (c Client) RotateAIAPIKey(ctx context.Context, id UUID) (*AIAPIKeyWithValue, error) { - path := fmt.Sprintf("/ai/ai-api-key/%v/rotate", id) +func (c Client) RotateAIAPIKey(ctx context.Context, id UUID) (*RotateAIAPIKeyResponse, error) { + path := fmt.Sprintf("/ai/api-key/%v/rotate", id) request, err := http.NewRequestWithContext(ctx, "POST", c.serverEndpoint+path, nil) if err != nil { @@ -305,7 +301,7 @@ func (c Client) RotateAIAPIKey(ctx context.Context, id UUID) (*AIAPIKeyWithValue return nil, fmt.Errorf("RotateAIAPIKey: http response: %w", err) } - bodyresp := new(AIAPIKeyWithValue) + bodyresp := new(RotateAIAPIKeyResponse) if err := prepareJSONResponse(response, bodyresp); err != nil { return nil, fmt.Errorf("RotateAIAPIKey: prepare Json response: %w", err) } @@ -1067,6 +1063,50 @@ func (c Client) GetModel(ctx context.Context, id UUID) (*GetModelResponse, error return bodyresp, nil } +// Get per-org Unit Of Measurement (UOM) consumption quota (UOM/min). Null means unlimited. UOM represents weighted units across different AI workloads (e.g., tokens for LLMs, minutes for TTS, pages for OCR). +func (c Client) GetUserOrgConsumptionQuota(ctx context.Context) (*OrgConsumptionQuotaResponse, error) { + path := "/ai/quota" + + request, err := http.NewRequestWithContext(ctx, "GET", c.serverEndpoint+path, nil) + if err != nil { + return nil, fmt.Errorf("GetUserOrgConsumptionQuota: new request: %w", err) + } + + request.Header.Add("User-Agent", c.getUserAgent()) + + if err := c.executeRequestInterceptors(ctx, request); err != nil { + return nil, fmt.Errorf("GetUserOrgConsumptionQuota: execute request editors: %w", err) + } + + if err := c.signRequest(request); err != nil { + return nil, fmt.Errorf("GetUserOrgConsumptionQuota: sign request: %w", err) + } + + if c.trace { + dumpRequest(request, "get-user-org-consumption-quota") + } + + response, err := c.httpClient.Do(request) + if err != nil { + return nil, fmt.Errorf("GetUserOrgConsumptionQuota: http client do: %w", err) + } + + if c.trace { + dumpResponse(response) + } + + if err := handleHTTPErrorResp(response); err != nil { + return nil, fmt.Errorf("GetUserOrgConsumptionQuota: http response: %w", err) + } + + bodyresp := new(OrgConsumptionQuotaResponse) + if err := prepareJSONResponse(response, bodyresp); err != nil { + return nil, fmt.Errorf("GetUserOrgConsumptionQuota: prepare Json response: %w", err) + } + + return bodyresp, nil +} + type ListAntiAffinityGroupsResponse struct { AntiAffinityGroups []AntiAffinityGroup `json:"anti-affinity-groups,omitempty"` } @@ -6864,6 +6904,8 @@ type CreateDBAASServicePGRequest struct { Migration *CreateDBAASServicePGRequestMigration `json:"migration,omitempty"` // postgresql.conf configuration values PGSettings *JSONSchemaPG `json:"pg-settings,omitempty"` + // System-wide settings for the pgaudit extension. + PgauditSettings *JSONSchemaPgaudit `json:"pgaudit-settings,omitempty"` // System-wide settings for pgbouncer. PgbouncerSettings *JSONSchemaPgbouncer `json:"pgbouncer-settings,omitempty"` // System-wide settings for pglookout. @@ -6992,6 +7034,8 @@ type UpdateDBAASServicePGRequest struct { Migration *UpdateDBAASServicePGRequestMigration `json:"migration,omitempty"` // postgresql.conf configuration values PGSettings *JSONSchemaPG `json:"pg-settings,omitempty"` + // System-wide settings for the pgaudit extension. + PgauditSettings *JSONSchemaPgaudit `json:"pgaudit-settings,omitempty"` // System-wide settings for pgbouncer. PgbouncerSettings *JSONSchemaPgbouncer `json:"pgbouncer-settings,omitempty"` // System-wide settings for pglookout. @@ -11115,8 +11159,8 @@ func (c Client) ListIAMRoles(ctx context.Context) (*ListIAMRolesResponse, error) } type CreateIAMRoleRequest struct { - // Policy - AssumeRolePolicy *IAMPolicy `json:"assume-role-policy,omitempty"` + // Assume Role Policy + AssumeRolePolicy *IAMAssumeRolePolicy `json:"assume-role-policy,omitempty"` // IAM Role description Description string `json:"description,omitempty" validate:"omitempty,gte=1,lte=255"` // Sets if the IAM Role Policy is editable or not (default: true). This setting cannot be changed after creation @@ -11272,6 +11316,8 @@ func (c Client) GetIAMRole(ctx context.Context, id UUID) (*IAMRole, error) { } type UpdateIAMRoleRequest struct { + // Assume Role Policy + AssumeRolePolicy *IAMAssumeRolePolicy `json:"assume-role-policy,omitempty"` // IAM Role description Description string `json:"description,omitempty" validate:"omitempty,gte=1,lte=255"` Labels Labels `json:"labels,omitempty"` @@ -11332,18 +11378,32 @@ func (c Client) UpdateIAMRole(ctx context.Context, id UUID, req UpdateIAMRoleReq return bodyresp, nil } -// Update IAM Assume role Policy -func (c Client) UpdateIAMRoleAssumePolicy(ctx context.Context, id UUID, req IAMPolicy) (*Operation, error) { - path := fmt.Sprintf("/iam-role/%v:assume-role-policy", id) +type AssumeIAMRoleResponse struct { + ExpiresAT string `json:"expires-at,omitempty"` + Key string `json:"key,omitempty"` + Name string `json:"name,omitempty"` + OrgID string `json:"org-id,omitempty"` + RoleID string `json:"role-id,omitempty"` + Secret string `json:"secret,omitempty"` +} + +type AssumeIAMRoleRequest struct { + // TTL in seconds for the generated access key (cannot exceed the max TTL defined in the targeted assume role) + Ttl int64 `json:"ttl" validate:"required,gt=0"` +} + +// [BETA] Request generation of key/secret that allow caller to assume target role +func (c Client) AssumeIAMRole(ctx context.Context, id UUID, req AssumeIAMRoleRequest) (*AssumeIAMRoleResponse, error) { + path := fmt.Sprintf("/iam-role/%v/assume", id) body, err := prepareJSONBody(req) if err != nil { - return nil, fmt.Errorf("UpdateIAMRoleAssumePolicy: prepare Json body: %w", err) + return nil, fmt.Errorf("AssumeIAMRole: prepare Json body: %w", err) } - request, err := http.NewRequestWithContext(ctx, "PUT", c.serverEndpoint+path, body) + request, err := http.NewRequestWithContext(ctx, "POST", c.serverEndpoint+path, body) if err != nil { - return nil, fmt.Errorf("UpdateIAMRoleAssumePolicy: new request: %w", err) + return nil, fmt.Errorf("AssumeIAMRole: new request: %w", err) } request.Header.Add("User-Agent", c.getUserAgent()) @@ -11351,20 +11411,20 @@ func (c Client) UpdateIAMRoleAssumePolicy(ctx context.Context, id UUID, req IAMP request.Header.Add("Content-Type", "application/json") if err := c.executeRequestInterceptors(ctx, request); err != nil { - return nil, fmt.Errorf("UpdateIAMRoleAssumePolicy: execute request editors: %w", err) + return nil, fmt.Errorf("AssumeIAMRole: execute request editors: %w", err) } if err := c.signRequest(request); err != nil { - return nil, fmt.Errorf("UpdateIAMRoleAssumePolicy: sign request: %w", err) + return nil, fmt.Errorf("AssumeIAMRole: sign request: %w", err) } if c.trace { - dumpRequest(request, "update-iam-role-assume-policy") + dumpRequest(request, "assume-iam-role") } response, err := c.httpClient.Do(request) if err != nil { - return nil, fmt.Errorf("UpdateIAMRoleAssumePolicy: http client do: %w", err) + return nil, fmt.Errorf("AssumeIAMRole: http client do: %w", err) } if c.trace { @@ -11372,12 +11432,12 @@ func (c Client) UpdateIAMRoleAssumePolicy(ctx context.Context, id UUID, req IAMP } if err := handleHTTPErrorResp(response); err != nil { - return nil, fmt.Errorf("UpdateIAMRoleAssumePolicy: http response: %w", err) + return nil, fmt.Errorf("AssumeIAMRole: http response: %w", err) } - bodyresp := new(Operation) + bodyresp := new(AssumeIAMRoleResponse) if err := prepareJSONResponse(response, bodyresp); err != nil { - return nil, fmt.Errorf("UpdateIAMRoleAssumePolicy: prepare Json response: %w", err) + return nil, fmt.Errorf("AssumeIAMRole: prepare Json response: %w", err) } return bodyresp, nil @@ -11434,70 +11494,6 @@ func (c Client) UpdateIAMRolePolicy(ctx context.Context, id UUID, req IAMPolicy) return bodyresp, nil } -type AssumeIAMRoleResponse struct { - Key string `json:"key,omitempty"` - Name string `json:"name,omitempty"` - OrgID string `json:"org-id,omitempty"` - RoleID string `json:"role-id,omitempty"` - Secret string `json:"secret,omitempty"` -} - -type AssumeIAMRoleRequest struct { - // TTL in seconds for the generated access key (cannot exceed the max TTL defined in the targeted assume role) - Ttl int64 `json:"ttl,omitempty" validate:"omitempty,gt=0"` -} - -// [BETA] Request generation of key/secret that allow caller to assume target role -func (c Client) AssumeIAMRole(ctx context.Context, targetRoleID UUID, req AssumeIAMRoleRequest) (*AssumeIAMRoleResponse, error) { - path := fmt.Sprintf("/iam-role/%v/assume", targetRoleID) - - body, err := prepareJSONBody(req) - if err != nil { - return nil, fmt.Errorf("AssumeIAMRole: prepare Json body: %w", err) - } - - request, err := http.NewRequestWithContext(ctx, "POST", c.serverEndpoint+path, body) - if err != nil { - return nil, fmt.Errorf("AssumeIAMRole: new request: %w", err) - } - - request.Header.Add("User-Agent", c.getUserAgent()) - - request.Header.Add("Content-Type", "application/json") - - if err := c.executeRequestInterceptors(ctx, request); err != nil { - return nil, fmt.Errorf("AssumeIAMRole: execute request editors: %w", err) - } - - if err := c.signRequest(request); err != nil { - return nil, fmt.Errorf("AssumeIAMRole: sign request: %w", err) - } - - if c.trace { - dumpRequest(request, "assume-iam-role") - } - - response, err := c.httpClient.Do(request) - if err != nil { - return nil, fmt.Errorf("AssumeIAMRole: http client do: %w", err) - } - - if c.trace { - dumpResponse(response) - } - - if err := handleHTTPErrorResp(response); err != nil { - return nil, fmt.Errorf("AssumeIAMRole: http response: %w", err) - } - - bodyresp := new(AssumeIAMRoleResponse) - if err := prepareJSONResponse(response, bodyresp); err != nil { - return nil, fmt.Errorf("AssumeIAMRole: prepare Json response: %w", err) - } - - return bodyresp, nil -} - // Private Network type ListInstancesResponseInstancesPrivateNetworks struct { // Private Network ID @@ -13992,6 +13988,50 @@ func (c Client) ScheduleKmsKeyDeletion(ctx context.Context, id UUID, req Schedul return bodyresp, nil } +// [BETA] Returns the live-balance of the current organization. +func (c Client) GetLiveBalance(ctx context.Context) (*LiveBalance, error) { + path := "/live-balance" + + request, err := http.NewRequestWithContext(ctx, "GET", c.serverEndpoint+path, nil) + if err != nil { + return nil, fmt.Errorf("GetLiveBalance: new request: %w", err) + } + + request.Header.Add("User-Agent", c.getUserAgent()) + + if err := c.executeRequestInterceptors(ctx, request); err != nil { + return nil, fmt.Errorf("GetLiveBalance: execute request editors: %w", err) + } + + if err := c.signRequest(request); err != nil { + return nil, fmt.Errorf("GetLiveBalance: sign request: %w", err) + } + + if c.trace { + dumpRequest(request, "get-live-balance") + } + + response, err := c.httpClient.Do(request) + if err != nil { + return nil, fmt.Errorf("GetLiveBalance: http client do: %w", err) + } + + if c.trace { + dumpResponse(response) + } + + if err := handleHTTPErrorResp(response); err != nil { + return nil, fmt.Errorf("GetLiveBalance: http response: %w", err) + } + + bodyresp := new(LiveBalance) + if err := prepareJSONResponse(response, bodyresp); err != nil { + return nil, fmt.Errorf("GetLiveBalance: prepare Json response: %w", err) + } + + return bodyresp, nil +} + type ListLoadBalancersResponse struct { LoadBalancers []LoadBalancer `json:"load-balancers,omitempty"` } @@ -16752,6 +16792,102 @@ func (c Client) GetSKSClusterAuthorityCert(ctx context.Context, id UUID, authori return bodyresp, nil } +type GenerateSKSKarpenterExoscaleNodeclassResponse struct { + ExoscaleNodeclass string `json:"exoscale-nodeclass,omitempty"` +} + +// Generate a Karpenter ExoscaleNodeClass manifest for an SKS cluster, including its default security group and feature flags if present +func (c Client) GenerateSKSKarpenterExoscaleNodeclass(ctx context.Context, id UUID) (*GenerateSKSKarpenterExoscaleNodeclassResponse, error) { + path := fmt.Sprintf("/sks-cluster/%v/generate-karpenter-exoscale-nodeclass", id) + + request, err := http.NewRequestWithContext(ctx, "PUT", c.serverEndpoint+path, nil) + if err != nil { + return nil, fmt.Errorf("GenerateSKSKarpenterExoscaleNodeclass: new request: %w", err) + } + + request.Header.Add("User-Agent", c.getUserAgent()) + + if err := c.executeRequestInterceptors(ctx, request); err != nil { + return nil, fmt.Errorf("GenerateSKSKarpenterExoscaleNodeclass: execute request editors: %w", err) + } + + if err := c.signRequest(request); err != nil { + return nil, fmt.Errorf("GenerateSKSKarpenterExoscaleNodeclass: sign request: %w", err) + } + + if c.trace { + dumpRequest(request, "generate-sks-karpenter-exoscale-nodeclass") + } + + response, err := c.httpClient.Do(request) + if err != nil { + return nil, fmt.Errorf("GenerateSKSKarpenterExoscaleNodeclass: http client do: %w", err) + } + + if c.trace { + dumpResponse(response) + } + + if err := handleHTTPErrorResp(response); err != nil { + return nil, fmt.Errorf("GenerateSKSKarpenterExoscaleNodeclass: http response: %w", err) + } + + bodyresp := new(GenerateSKSKarpenterExoscaleNodeclassResponse) + if err := prepareJSONResponse(response, bodyresp); err != nil { + return nil, fmt.Errorf("GenerateSKSKarpenterExoscaleNodeclass: prepare Json response: %w", err) + } + + return bodyresp, nil +} + +type GenerateSKSKarpenterNodepoolResponse struct { + Nodepool string `json:"nodepool,omitempty"` +} + +// Generate a Karpenter NodePool manifest with minimal configuration for an SKS cluster +func (c Client) GenerateSKSKarpenterNodepool(ctx context.Context, id UUID) (*GenerateSKSKarpenterNodepoolResponse, error) { + path := fmt.Sprintf("/sks-cluster/%v/generate-karpenter-nodepool", id) + + request, err := http.NewRequestWithContext(ctx, "PUT", c.serverEndpoint+path, nil) + if err != nil { + return nil, fmt.Errorf("GenerateSKSKarpenterNodepool: new request: %w", err) + } + + request.Header.Add("User-Agent", c.getUserAgent()) + + if err := c.executeRequestInterceptors(ctx, request); err != nil { + return nil, fmt.Errorf("GenerateSKSKarpenterNodepool: execute request editors: %w", err) + } + + if err := c.signRequest(request); err != nil { + return nil, fmt.Errorf("GenerateSKSKarpenterNodepool: sign request: %w", err) + } + + if c.trace { + dumpRequest(request, "generate-sks-karpenter-nodepool") + } + + response, err := c.httpClient.Do(request) + if err != nil { + return nil, fmt.Errorf("GenerateSKSKarpenterNodepool: http client do: %w", err) + } + + if c.trace { + dumpResponse(response) + } + + if err := handleHTTPErrorResp(response); err != nil { + return nil, fmt.Errorf("GenerateSKSKarpenterNodepool: http response: %w", err) + } + + bodyresp := new(GenerateSKSKarpenterNodepoolResponse) + if err := prepareJSONResponse(response, bodyresp); err != nil { + return nil, fmt.Errorf("GenerateSKSKarpenterNodepool: prepare Json response: %w", err) + } + + return bodyresp, nil +} + type GetSKSClusterInspectionResponse map[string]any // Helps troubleshoot common problems when deploying a kubernetes cluster. Inspections run every couple of minutes. @@ -18801,6 +18937,279 @@ func (c Client) UpdateUserRole(ctx context.Context, id UUID, req UpdateUserRoleR return bodyresp, nil } +type ListVpcsResponse struct { + Vpcs []ListVpcResponseEntry `json:"vpcs,omitempty"` +} + +// FindListVpcResponseEntry attempts to find an ListVpcResponseEntry by nameOrID. +func (l ListVpcsResponse) FindListVpcResponseEntry(nameOrID string) (ListVpcResponseEntry, error) { + var result []ListVpcResponseEntry + for i, elem := range l.Vpcs { + if string(elem.Name) == nameOrID || string(elem.ID) == nameOrID { + result = append(result, l.Vpcs[i]) + } + } + if len(result) == 1 { + return result[0], nil + } + + if len(result) > 1 { + return ListVpcResponseEntry{}, fmt.Errorf("%q too many found in ListVpcsResponse: %w", nameOrID, ErrConflict) + } + + return ListVpcResponseEntry{}, fmt.Errorf("%q not found in ListVpcsResponse: %w", nameOrID, ErrNotFound) +} + +// [BETA] List VPCs +func (c Client) ListVpcs(ctx context.Context) (*ListVpcsResponse, error) { + path := "/vpc" + + request, err := http.NewRequestWithContext(ctx, "GET", c.serverEndpoint+path, nil) + if err != nil { + return nil, fmt.Errorf("ListVpcs: new request: %w", err) + } + + request.Header.Add("User-Agent", c.getUserAgent()) + + if err := c.executeRequestInterceptors(ctx, request); err != nil { + return nil, fmt.Errorf("ListVpcs: execute request editors: %w", err) + } + + if err := c.signRequest(request); err != nil { + return nil, fmt.Errorf("ListVpcs: sign request: %w", err) + } + + if c.trace { + dumpRequest(request, "list-vpcs") + } + + response, err := c.httpClient.Do(request) + if err != nil { + return nil, fmt.Errorf("ListVpcs: http client do: %w", err) + } + + if c.trace { + dumpResponse(response) + } + + if err := handleHTTPErrorResp(response); err != nil { + return nil, fmt.Errorf("ListVpcs: http response: %w", err) + } + + bodyresp := new(ListVpcsResponse) + if err := prepareJSONResponse(response, bodyresp); err != nil { + return nil, fmt.Errorf("ListVpcs: prepare Json response: %w", err) + } + + return bodyresp, nil +} + +type CreateVpcRequest struct { + // VPC description + Description string `json:"description,omitempty" validate:"omitempty,lte=4096"` + Labels Labels `json:"labels,omitempty"` + // VPC name + Name string `json:"name" validate:"required,gte=1,lte=255"` +} + +// [BETA] Create a VPC +func (c Client) CreateVpc(ctx context.Context, req CreateVpcRequest) (*Operation, error) { + path := "/vpc" + + body, err := prepareJSONBody(req) + if err != nil { + return nil, fmt.Errorf("CreateVpc: prepare Json body: %w", err) + } + + request, err := http.NewRequestWithContext(ctx, "POST", c.serverEndpoint+path, body) + if err != nil { + return nil, fmt.Errorf("CreateVpc: new request: %w", err) + } + + request.Header.Add("User-Agent", c.getUserAgent()) + + request.Header.Add("Content-Type", "application/json") + + if err := c.executeRequestInterceptors(ctx, request); err != nil { + return nil, fmt.Errorf("CreateVpc: execute request editors: %w", err) + } + + if err := c.signRequest(request); err != nil { + return nil, fmt.Errorf("CreateVpc: sign request: %w", err) + } + + if c.trace { + dumpRequest(request, "create-vpc") + } + + response, err := c.httpClient.Do(request) + if err != nil { + return nil, fmt.Errorf("CreateVpc: http client do: %w", err) + } + + if c.trace { + dumpResponse(response) + } + + if err := handleHTTPErrorResp(response); err != nil { + return nil, fmt.Errorf("CreateVpc: http response: %w", err) + } + + bodyresp := new(Operation) + if err := prepareJSONResponse(response, bodyresp); err != nil { + return nil, fmt.Errorf("CreateVpc: prepare Json response: %w", err) + } + + return bodyresp, nil +} + +// [BETA] Delete a VPC +func (c Client) DeleteVpc(ctx context.Context, id UUID) (*Operation, error) { + path := fmt.Sprintf("/vpc/%v", id) + + request, err := http.NewRequestWithContext(ctx, "DELETE", c.serverEndpoint+path, nil) + if err != nil { + return nil, fmt.Errorf("DeleteVpc: new request: %w", err) + } + + request.Header.Add("User-Agent", c.getUserAgent()) + + if err := c.executeRequestInterceptors(ctx, request); err != nil { + return nil, fmt.Errorf("DeleteVpc: execute request editors: %w", err) + } + + if err := c.signRequest(request); err != nil { + return nil, fmt.Errorf("DeleteVpc: sign request: %w", err) + } + + if c.trace { + dumpRequest(request, "delete-vpc") + } + + response, err := c.httpClient.Do(request) + if err != nil { + return nil, fmt.Errorf("DeleteVpc: http client do: %w", err) + } + + if c.trace { + dumpResponse(response) + } + + if err := handleHTTPErrorResp(response); err != nil { + return nil, fmt.Errorf("DeleteVpc: http response: %w", err) + } + + bodyresp := new(Operation) + if err := prepareJSONResponse(response, bodyresp); err != nil { + return nil, fmt.Errorf("DeleteVpc: prepare Json response: %w", err) + } + + return bodyresp, nil +} + +// [BETA] Retrieve VPC details +func (c Client) GetVpc(ctx context.Context, id UUID) (*Vpc, error) { + path := fmt.Sprintf("/vpc/%v", id) + + request, err := http.NewRequestWithContext(ctx, "GET", c.serverEndpoint+path, nil) + if err != nil { + return nil, fmt.Errorf("GetVpc: new request: %w", err) + } + + request.Header.Add("User-Agent", c.getUserAgent()) + + if err := c.executeRequestInterceptors(ctx, request); err != nil { + return nil, fmt.Errorf("GetVpc: execute request editors: %w", err) + } + + if err := c.signRequest(request); err != nil { + return nil, fmt.Errorf("GetVpc: sign request: %w", err) + } + + if c.trace { + dumpRequest(request, "get-vpc") + } + + response, err := c.httpClient.Do(request) + if err != nil { + return nil, fmt.Errorf("GetVpc: http client do: %w", err) + } + + if c.trace { + dumpResponse(response) + } + + if err := handleHTTPErrorResp(response); err != nil { + return nil, fmt.Errorf("GetVpc: http response: %w", err) + } + + bodyresp := new(Vpc) + if err := prepareJSONResponse(response, bodyresp); err != nil { + return nil, fmt.Errorf("GetVpc: prepare Json response: %w", err) + } + + return bodyresp, nil +} + +type UpdateVpcRequest struct { + // VPC description + Description *string `json:"description,omitempty" validate:"omitempty,lte=4096"` + Labels Labels `json:"labels"` + // VPC name + Name *string `json:"name,omitempty" validate:"omitempty,gte=1,lte=255"` +} + +// [BETA] Update a VPC +func (c Client) UpdateVpc(ctx context.Context, id UUID, req UpdateVpcRequest) (*Vpc, error) { + path := fmt.Sprintf("/vpc/%v", id) + + body, err := prepareJSONBody(req) + if err != nil { + return nil, fmt.Errorf("UpdateVpc: prepare Json body: %w", err) + } + + request, err := http.NewRequestWithContext(ctx, "PUT", c.serverEndpoint+path, body) + if err != nil { + return nil, fmt.Errorf("UpdateVpc: new request: %w", err) + } + + request.Header.Add("User-Agent", c.getUserAgent()) + + request.Header.Add("Content-Type", "application/json") + + if err := c.executeRequestInterceptors(ctx, request); err != nil { + return nil, fmt.Errorf("UpdateVpc: execute request editors: %w", err) + } + + if err := c.signRequest(request); err != nil { + return nil, fmt.Errorf("UpdateVpc: sign request: %w", err) + } + + if c.trace { + dumpRequest(request, "update-vpc") + } + + response, err := c.httpClient.Do(request) + if err != nil { + return nil, fmt.Errorf("UpdateVpc: http client do: %w", err) + } + + if c.trace { + dumpResponse(response) + } + + if err := handleHTTPErrorResp(response); err != nil { + return nil, fmt.Errorf("UpdateVpc: http response: %w", err) + } + + bodyresp := new(Vpc) + if err := prepareJSONResponse(response, bodyresp); err != nil { + return nil, fmt.Errorf("UpdateVpc: prepare Json response: %w", err) + } + + return bodyresp, nil +} + type ListZonesResponse struct { Zones []Zone `json:"zones,omitempty"` } diff --git a/vendor/github.com/exoscale/egoscale/v3/schemas.go b/vendor/github.com/exoscale/egoscale/v3/schemas.go index d6562f8a6..a058509d0 100644 --- a/vendor/github.com/exoscale/egoscale/v3/schemas.go +++ b/vendor/github.com/exoscale/egoscale/v3/schemas.go @@ -74,24 +74,26 @@ type AccessKeyResource struct { ResourceType AccessKeyResourceResourceType `json:"resource-type,omitempty"` } -// AI API key metadata (without value) +// AI API key metadata type AIAPIKey struct { // Creation timestamp - CreatedAT time.Time `json:"created-at,omitempty"` + CreatedAT time.Time `json:"created-at" validate:"required"` // AI API key ID - ID UUID `json:"id,omitempty"` + ID UUID `json:"id" validate:"required"` // Human-readable name for the AI API key - Name string `json:"name,omitempty"` + Name string `json:"name" validate:"required"` // Organization UUID that owns this key - OrgUuid UUID `json:"org-uuid,omitempty"` + OrgUuid UUID `json:"org-uuid" validate:"required"` // Key scope: 'public' for all deployments, or a specific deployment UUID - Scope string `json:"scope,omitempty"` + Scope string `json:"scope" validate:"required"` // Last update timestamp - UpdatedAT time.Time `json:"updated-at,omitempty"` + UpdatedAT time.Time `json:"updated-at" validate:"required"` } -// AI API key with plaintext value -type AIAPIKeyWithValue struct { +// AI API key plaintext value +type AIAPIKeyValue struct { + // Plaintext AI API key value + Value string `json:"value" validate:"required"` } // Anti-affinity Group @@ -112,6 +114,14 @@ type AntiAffinityGroupRef struct { ID UUID `json:"id,omitempty"` } +// Usage breakdown for one API key, grouped by model +type APIKeyUsageEntry struct { + // Map of model-uuid to accumulated counters. Keys are model UUIDs. + Models map[string]ModelUsageCounters `json:"models" validate:"required"` + // Organization that owns this API key + OrganizationID UUID `json:"organization-id" validate:"required"` +} + type BlockStorageSnapshotState string const ( @@ -172,6 +182,8 @@ type BlockStorageVolume struct { Blocksize int64 `json:"blocksize,omitempty" validate:"omitempty,gte=0"` // Volume creation date CreatedAT time.Time `json:"created-at,omitempty"` + // Indicates if the block-storage volume is encrypted + Encrypted *bool `json:"encrypted,omitempty"` // Volume ID ID UUID `json:"id,omitempty"` // Target Instance @@ -199,7 +211,23 @@ type CreateAIAPIKeyRequest struct { Scope string `json:"scope" validate:"required"` } -// Deployment an AI model onto a set of GPUs +// Create AI API key response +type CreateAIAPIKeyResponse struct { + // Creation timestamp + CreatedAT time.Time `json:"created-at" validate:"required"` + // AI API key ID + ID UUID `json:"id" validate:"required"` + // Human-readable name for the AI API key + Name string `json:"name" validate:"required"` + // Organization UUID that owns this key + OrgUuid UUID `json:"org-uuid" validate:"required"` + // Key scope: 'public' for all deployments, or a specific deployment UUID + Scope string `json:"scope" validate:"required"` + // Last update timestamp + UpdatedAT time.Time `json:"updated-at" validate:"required"` +} + +// Deploy an AI model onto a set of GPUs type CreateDeploymentRequest struct { // Number of GPUs (1-8) GpuCount int64 `json:"gpu-count" validate:"required,gte=1"` @@ -209,7 +237,8 @@ type CreateDeploymentRequest struct { InferenceEngineParameters []string `json:"inference-engine-parameters,omitempty"` // Inference engine version InferenceEngineVersion InferenceEngineVersion `json:"inference-engine-version,omitempty"` - Model *ModelRef `json:"model" validate:"required"` + // Model reference. Provide either id or name. + Model *ModelRef `json:"model" validate:"required"` // Deployment name Name string `json:"name" validate:"required,gte=1"` // Number of replicas (>=1) @@ -223,10 +252,10 @@ const ( ) type CreateKmsKeyRequest struct { - Description string `json:"description" validate:"required"` - MultiZone *bool `json:"multi-zone" validate:"required"` + Description string `json:"description,omitempty"` + MultiZone *bool `json:"multi-zone,omitempty"` Name string `json:"name" validate:"required"` - Usage CreateKmsKeyRequestUsage `json:"usage" validate:"required"` + Usage CreateKmsKeyRequestUsage `json:"usage,omitempty"` } type CreateKmsKeyResponseSource string @@ -253,6 +282,7 @@ type CreateKmsKeyResponse struct { Revision *RevisionStamp `json:"revision" validate:"required"` Source CreateKmsKeyResponseSource `json:"source" validate:"required"` Status CreateKmsKeyResponseStatus `json:"status" validate:"required"` + StatusSince time.Time `json:"status-since" validate:"required"` Usage string `json:"usage" validate:"required"` } @@ -1297,6 +1327,8 @@ type DBAASServiceMysql struct { BackupSchedule *DBAASServiceMysqlBackupSchedule `json:"backup-schedule,omitempty"` // List of backups for the service Backups []DBAASServiceBackup `json:"backups,omitempty"` + // The minimum amount of time in seconds to keep binlog entries before deletion. This may be extended for services that require binlog entries for longer than the default for example if using the MySQL Debezium Kafka connector. + BinlogRetentionPeriod int64 `json:"binlog-retention-period,omitempty" validate:"omitempty,gt=0"` // Service component information objects Components []DBAASServiceMysqlComponents `json:"components,omitempty"` // MySQL connection information properties @@ -1599,6 +1631,8 @@ type DBAASServicePG struct { Notifications []DBAASServiceNotification `json:"notifications,omitempty"` // postgresql.conf configuration values PGSettings *JSONSchemaPG `json:"pg-settings,omitempty"` + // System-wide settings for the pgaudit extension. + PgauditSettings *JSONSchemaPgaudit `json:"pgaudit-settings,omitempty"` // System-wide settings for pgbouncer. PgbouncerSettings *JSONSchemaPgbouncer `json:"pgbouncer-settings,omitempty"` // System-wide settings for pglookout. @@ -2387,25 +2421,41 @@ type GenerateDataKeyResponse struct { Plaintext []byte `json:"plaintext" validate:"required"` } +// Get AI API key response +type GetAIAPIKeyResponse struct { + // Creation timestamp + CreatedAT time.Time `json:"created-at" validate:"required"` + // AI API key ID + ID UUID `json:"id" validate:"required"` + // Human-readable name for the AI API key + Name string `json:"name" validate:"required"` + // Organization UUID that owns this key + OrgUuid UUID `json:"org-uuid" validate:"required"` + // Key scope: 'public' for all deployments, or a specific deployment UUID + Scope string `json:"scope" validate:"required"` + // Last update timestamp + UpdatedAT time.Time `json:"updated-at" validate:"required"` +} + // GPU usage for all organizations type GetConfederatioUsageResponse struct { - OrganizationsUsages map[string]OrganizationUsage `json:"organizations_usages" validate:"required"` + OrganizationsUsages map[string]OrganizationUsage `json:"organizations-usages" validate:"required"` } // A single log entry type GetDeploymentLogsEntry struct { // Log message content - Message string `json:"message,omitempty"` + Message string `json:"message" validate:"required"` // Node identifier - Node string `json:"node,omitempty"` + Node string `json:"node" validate:"required"` // Timestamp of the log entry - Time string `json:"time,omitempty"` + Time time.Time `json:"time" validate:"required"` } // Deployment logs type GetDeploymentLogsResponse struct { // List of log entries - Logs []GetDeploymentLogsEntry `json:"logs,omitempty"` + Logs []GetDeploymentLogsEntry `json:"logs" validate:"required"` } type GetDeploymentResponseState string @@ -2413,44 +2463,48 @@ type GetDeploymentResponseState string const ( GetDeploymentResponseStateReady GetDeploymentResponseState = "ready" GetDeploymentResponseStateCreating GetDeploymentResponseState = "creating" + GetDeploymentResponseStatePreparing GetDeploymentResponseState = "preparing" GetDeploymentResponseStateError GetDeploymentResponseState = "error" GetDeploymentResponseStateDeploying GetDeploymentResponseState = "deploying" + GetDeploymentResponseStateScaling GetDeploymentResponseState = "scaling" + GetDeploymentResponseStateUpdating GetDeploymentResponseState = "updating" ) // AI deployment type GetDeploymentResponse struct { // Creation time - CreatedAT time.Time `json:"created-at,omitempty"` - // Deployment URL (nullable) - DeploymentURL string `json:"deployment-url,omitempty"` + CreatedAT time.Time `json:"created-at" validate:"required"` + // Deployment inference endpoint URL + DeploymentURL string `json:"deployment-url" validate:"required"` // Number of GPUs - GpuCount int64 `json:"gpu-count,omitempty" validate:"omitempty,gte=1"` + GpuCount int64 `json:"gpu-count" validate:"required,gte=1"` // GPU type family - GpuType string `json:"gpu-type,omitempty" validate:"omitempty,gte=1"` + GpuType string `json:"gpu-type" validate:"required,gte=1"` // Deployment ID - ID UUID `json:"id,omitempty"` + ID UUID `json:"id" validate:"required"` // Optional extra inference engine server CLI args - InferenceEngineParameters []string `json:"inference-engine-parameters,omitempty"` + InferenceEngineParameters []string `json:"inference-engine-parameters" validate:"required"` // Inference engine version - InferenceEngineVersion InferenceEngineVersion `json:"inference-engine-version,omitempty"` - Model *ModelRef `json:"model,omitempty"` + InferenceEngineVersion InferenceEngineVersion `json:"inference-engine-version" validate:"required"` + // Model reference. Provide either id or name. + Model *ModelRef `json:"model" validate:"required"` // Deployment name - Name string `json:"name,omitempty" validate:"omitempty,gte=1"` + Name string `json:"name" validate:"required,gte=1"` // Number of replicas (>=0) - Replicas int64 `json:"replicas,omitempty" validate:"omitempty,gte=0"` + Replicas int64 `json:"replicas" validate:"required,gte=0"` // Service level - ServiceLevel string `json:"service-level,omitempty" validate:"omitempty,gte=1"` + ServiceLevel string `json:"service-level" validate:"required,gte=1"` // Deployment state - State GetDeploymentResponseState `json:"state,omitempty"` + State GetDeploymentResponseState `json:"state" validate:"required"` // Deployment state details - StateDetails string `json:"state-details,omitempty"` + StateDetails string `json:"state-details" validate:"required"` // Update time - UpdatedAT time.Time `json:"updated-at,omitempty"` + UpdatedAT time.Time `json:"updated-at" validate:"required"` } // List of allowed inference-engine parameters type GetInferenceEngineHelpResponse struct { - Parameters []InferenceEngineParameterEntry `json:"parameters,omitempty"` + Parameters []InferenceEngineParameterEntry `json:"parameters" validate:"required"` } type GetKmsKeyResponseSource string @@ -2498,23 +2552,23 @@ const ( // AI model type GetModelResponse struct { // Creation time - CreatedAT time.Time `json:"created-at,omitempty"` + CreatedAT time.Time `json:"created-at" validate:"required"` // Model ID - ID UUID `json:"id,omitempty"` - // Model size (nullable) - ModelSize int64 `json:"model-size,omitempty" validate:"omitempty,gte=0"` + ID UUID `json:"id" validate:"required"` + // Model size in bytes + ModelSize int64 `json:"model-size" validate:"required,gte=0"` // Model name - Name string `json:"name,omitempty" validate:"omitempty,gte=1"` + Name string `json:"name" validate:"required,gte=1"` // Model state - State GetModelResponseState `json:"state,omitempty"` + State GetModelResponseState `json:"state" validate:"required"` // Update time - UpdatedAT time.Time `json:"updated-at,omitempty"` + UpdatedAT time.Time `json:"updated-at" validate:"required"` } // GPU usage for an organization type GetOrganizationUsageResponse struct { // Total GPU count - Gpu int64 `json:"gpu,omitempty" validate:"omitempty,gte=0"` + Gpu int64 `json:"gpu" validate:"required,gte=0"` } // IAM API Key @@ -2539,6 +2593,12 @@ type IAMAPIKeyCreated struct { Secret string `json:"secret,omitempty"` } +// Assume Role Policy +type IAMAssumeRolePolicy struct { + // IAM Assume Role Policy rules + Rules []IAMServicePolicyRule `json:"rules,omitempty"` +} + type IAMPolicyDefaultServiceStrategy string const ( @@ -2556,8 +2616,8 @@ type IAMPolicy struct { // IAM Role type IAMRole struct { - // Policy - AssumeRolePolicy *IAMPolicy `json:"assume-role-policy,omitempty"` + // Assume Role Policy + AssumeRolePolicy *IAMAssumeRolePolicy `json:"assume-role-policy,omitempty"` // IAM Role description Description string `json:"description,omitempty" validate:"omitempty,gte=1,lte=255"` // IAM Role mutability @@ -2630,8 +2690,35 @@ const ( InferenceEngineVersion0180 InferenceEngineVersion = "0.18.0" InferenceEngineVersion0181 InferenceEngineVersion = "0.18.1" InferenceEngineVersion0190 InferenceEngineVersion = "0.19.0" + InferenceEngineVersion0191 InferenceEngineVersion = "0.19.1" + InferenceEngineVersion0200 InferenceEngineVersion = "0.20.0" + InferenceEngineVersion0201 InferenceEngineVersion = "0.20.1" + InferenceEngineVersion0202 InferenceEngineVersion = "0.20.2" + InferenceEngineVersion0210 InferenceEngineVersion = "0.21.0" + InferenceEngineVersion0220 InferenceEngineVersion = "0.22.0" + InferenceEngineVersion0221 InferenceEngineVersion = "0.22.1" ) +// Router flush payload: the router's full in-memory usage map with flush identity fields +type IngestMeteringRequest struct { + // ISO-8601 UTC timestamp when the flush snapshot was created (truncated to minute boundary for bucketing) + CreatedAT time.Time `json:"created-at" validate:"required"` + // UUID identifying this flush; used for idempotent deduplication + FlushID UUID `json:"flush-id" validate:"required"` + // Router instance identifier that produced this flush + RouterID string `json:"router-id" validate:"required,gte=1"` + // Map of api-key-uuid to usage entry. Keys are API key UUIDs. Mirrors the router's in-memory accumulator structure directly. + Usage map[string]APIKeyUsageEntry `json:"usage" validate:"required"` +} + +// Result of a metering ingest operation +type IngestMeteringResponse struct { + // True if flush-id was already processed (idempotent retry) + Duplicate *bool `json:"duplicate,omitempty"` + // Number of rows affected (inserted or updated) in usage_minutely; 0 if duplicate flush-id + Upserted int `json:"upserted" validate:"required"` +} + // Private Network type InstancePrivateNetworks struct { // Private Network ID @@ -2650,6 +2737,8 @@ type Instance struct { CreatedAT time.Time `json:"created-at,omitempty"` // Deploy target reference DeployTarget *DeployTarget `json:"deploy-target,omitempty"` + // Indicates if the root volume of the instance is encrypted + DiskEncrypted *bool `json:"disk-encrypted,omitempty"` // Instance disk size in GiB DiskSize int64 `json:"disk-size,omitempty" validate:"omitempty,gte=10,lte=51200"` // Instance Elastic IPs @@ -2795,6 +2884,7 @@ const ( InstanceTypeFamilyGpu InstanceTypeFamily = "gpu" InstanceTypeFamilyMemory InstanceTypeFamily = "memory" InstanceTypeFamilyGpua5000 InstanceTypeFamily = "gpua5000" + InstanceTypeFamilyGpub300 InstanceTypeFamily = "gpub300" InstanceTypeFamilyGpurtx6000pro InstanceTypeFamily = "gpurtx6000pro" InstanceTypeFamilyStorage InstanceTypeFamily = "storage" InstanceTypeFamilyStandard InstanceTypeFamily = "standard" @@ -2842,9 +2932,9 @@ type InstanceType struct { // Instance type with authorization status type InstanceTypeEntry struct { // Whether this instance type is authorized based on server availability - Authorized *bool `json:"authorized,omitempty"` + Authorized *bool `json:"authorized" validate:"required"` // GPU family name - Family string `json:"family,omitempty"` + Family string `json:"family" validate:"required"` } // Instance type reference @@ -3875,6 +3965,58 @@ type JSONSchemaPG struct { Wal *JSONSchemaPGWal `json:"wal,omitempty"` } +type JSONSchemaPgauditLogLevel string + +const ( + JSONSchemaPgauditLogLevelDebug1 JSONSchemaPgauditLogLevel = "debug1" + JSONSchemaPgauditLogLevelDebug2 JSONSchemaPgauditLogLevel = "debug2" + JSONSchemaPgauditLogLevelDebug3 JSONSchemaPgauditLogLevel = "debug3" + JSONSchemaPgauditLogLevelDebug4 JSONSchemaPgauditLogLevel = "debug4" + JSONSchemaPgauditLogLevelDebug5 JSONSchemaPgauditLogLevel = "debug5" + JSONSchemaPgauditLogLevelInfo JSONSchemaPgauditLogLevel = "info" + JSONSchemaPgauditLogLevelNotice JSONSchemaPgauditLogLevel = "notice" + JSONSchemaPgauditLogLevelWarning JSONSchemaPgauditLogLevel = "warning" + JSONSchemaPgauditLogLevelLog JSONSchemaPgauditLogLevel = "log" +) + +// System-wide settings for the pgaudit extension. +type JSONSchemaPgaudit struct { + // Enable pgaudit extension. When enabled, pgaudit extension will be automatically installed.Otherwise, extension will be uninstalled but auditing configurations will be preserved. + FeatureEnabled *bool `json:"feature_enabled,omitempty"` + // Specifies which classes of statements will be logged by session audit logging. + Log []string `json:"log,omitempty"` + // Specifies that session logging should be enabled in the case where all relations + // in a statement are in pg_catalog. + LogCatalog *bool `json:"log_catalog,omitempty"` + // Specifies whether log messages will be visible to a client process such as psql. + LogClient *bool `json:"log_client,omitempty"` + // Specifies the log level that will be used for log entries. + LogLevel JSONSchemaPgauditLogLevel `json:"log_level,omitempty"` + // Crop parameters representation and whole statements if they exceed this threshold. + // A (default) value of -1 disable the truncation. + LogMaxStringLength int `json:"log_max_string_length,omitempty" validate:"omitempty,gte=-1,lte=102400"` + // This GUC allows to turn off logging nested statements, that is, statements that are + // executed as part of another ExecutorRun. + LogNestedStatements *bool `json:"log_nested_statements,omitempty"` + // Specifies that audit logging should include the parameters that were passed with the statement. + LogParameter *bool `json:"log_parameter,omitempty"` + // Specifies that parameter values longer than this setting (in bytes) should not be logged, + // but replaced with . + LogParameterMaxSize int `json:"log_parameter_max_size,omitempty"` + // Specifies whether session audit logging should create a separate log entry + // for each relation (TABLE, VIEW, etc.) referenced in a SELECT or DML statement. + LogRelation *bool `json:"log_relation,omitempty"` + // Log Rows + LogRows *bool `json:"log_rows,omitempty"` + // Specifies whether logging will include the statement text and parameters (if enabled). + LogStatement *bool `json:"log_statement,omitempty"` + // Specifies whether logging will include the statement text and parameters with + // the first log entry for a statement/substatement combination or with every entry. + LogStatementOnce *bool `json:"log_statement_once,omitempty"` + // Specifies the master role to use for object audit logging. + Role string `json:"role,omitempty" validate:"omitempty,lte=64"` +} + type JSONSchemaPgbouncerAutodbPoolMode string const ( @@ -4042,17 +4184,33 @@ type Labels map[string]string // List of AI API keys type ListAIAPIKeysResponse struct { - AIAPIKeys []AIAPIKey `json:"ai-api-keys" validate:"required"` + AIAPIKeys []ListAIAPIKeysResponseEntry `json:"ai-api-keys" validate:"required"` +} + +// AI API key list entry +type ListAIAPIKeysResponseEntry struct { + // Creation timestamp + CreatedAT time.Time `json:"created-at" validate:"required"` + // AI API key ID + ID UUID `json:"id" validate:"required"` + // Human-readable name for the AI API key + Name string `json:"name" validate:"required"` + // Organization UUID that owns this key + OrgUuid UUID `json:"org-uuid" validate:"required"` + // Key scope: 'public' for all deployments, or a specific deployment UUID + Scope string `json:"scope" validate:"required"` + // Last update timestamp + UpdatedAT time.Time `json:"updated-at" validate:"required"` } // List of available instance types with authorization status type ListAIInstanceTypesResponse struct { - InstanceTypes []InstanceTypeEntry `json:"instance-types,omitempty"` + InstanceTypes []InstanceTypeEntry `json:"instance-types" validate:"required"` } -// AI model list +// AI deployment list type ListDeploymentsResponse struct { - Deployments []ListDeploymentsResponseEntry `json:"deployments,omitempty"` + Deployments []ListDeploymentsResponseEntry `json:"deployments" validate:"required"` } type ListDeploymentsResponseEntryState string @@ -4060,33 +4218,37 @@ type ListDeploymentsResponseEntryState string const ( ListDeploymentsResponseEntryStateReady ListDeploymentsResponseEntryState = "ready" ListDeploymentsResponseEntryStateCreating ListDeploymentsResponseEntryState = "creating" + ListDeploymentsResponseEntryStatePreparing ListDeploymentsResponseEntryState = "preparing" ListDeploymentsResponseEntryStateError ListDeploymentsResponseEntryState = "error" ListDeploymentsResponseEntryStateDeploying ListDeploymentsResponseEntryState = "deploying" + ListDeploymentsResponseEntryStateScaling ListDeploymentsResponseEntryState = "scaling" + ListDeploymentsResponseEntryStateUpdating ListDeploymentsResponseEntryState = "updating" ) // AI deployment type ListDeploymentsResponseEntry struct { // Creation time - CreatedAT time.Time `json:"created-at,omitempty"` - // Deployment URL (nullable) - DeploymentURL string `json:"deployment-url,omitempty"` + CreatedAT time.Time `json:"created-at" validate:"required"` + // Deployment inference endpoint URL + DeploymentURL string `json:"deployment-url" validate:"required"` // Number of GPUs - GpuCount int64 `json:"gpu-count,omitempty" validate:"omitempty,gte=1"` + GpuCount int64 `json:"gpu-count" validate:"required,gte=1"` // GPU type family - GpuType string `json:"gpu-type,omitempty" validate:"omitempty,gte=1"` + GpuType string `json:"gpu-type" validate:"required,gte=1"` // Deployment ID - ID UUID `json:"id,omitempty"` - Model *ModelRef `json:"model,omitempty"` + ID UUID `json:"id" validate:"required"` + // Model reference. Provide either id or name. + Model *ModelRef `json:"model" validate:"required"` // Deployment name - Name string `json:"name,omitempty" validate:"omitempty,gte=1"` + Name string `json:"name" validate:"required,gte=1"` // Number of replicas (>=0) - Replicas int64 `json:"replicas,omitempty" validate:"omitempty,gte=0"` + Replicas int64 `json:"replicas" validate:"required,gte=0"` // Service level - ServiceLevel string `json:"service-level,omitempty" validate:"omitempty,gte=1"` + ServiceLevel string `json:"service-level" validate:"required,gte=1"` // Deployment state - State ListDeploymentsResponseEntryState `json:"state,omitempty"` + State ListDeploymentsResponseEntryState `json:"state" validate:"required"` // Update time - UpdatedAT time.Time `json:"updated-at,omitempty"` + UpdatedAT time.Time `json:"updated-at" validate:"required"` } type ListKmsKeyRotationsResponse struct { @@ -4136,7 +4298,7 @@ type ListKmsKeysResponseEntry struct { // AI model list type ListModelsResponse struct { - Models []ListModelsResponseEntry `json:"models,omitempty"` + Models []ListModelsResponseEntry `json:"models" validate:"required"` } type ListModelsResponseEntryState string @@ -4152,17 +4314,38 @@ const ( // AI model type ListModelsResponseEntry struct { // Creation time - CreatedAT time.Time `json:"created-at,omitempty"` + CreatedAT time.Time `json:"created-at" validate:"required"` // Model ID - ID UUID `json:"id,omitempty"` - // Model size (nullable) - ModelSize int64 `json:"model-size,omitempty" validate:"omitempty,gte=0"` + ID UUID `json:"id" validate:"required"` + // Model size in bytes + ModelSize int64 `json:"model-size" validate:"required,gte=0"` // Model name - Name string `json:"name,omitempty" validate:"omitempty,gte=1"` + Name string `json:"name" validate:"required,gte=1"` // Model state - State ListModelsResponseEntryState `json:"state,omitempty"` + State ListModelsResponseEntryState `json:"state" validate:"required"` // Update time - UpdatedAT time.Time `json:"updated-at,omitempty"` + UpdatedAT time.Time `json:"updated-at" validate:"required"` +} + +// VPC +type ListVpcResponseEntry struct { + // VPC creation date + CreatedAT time.Time `json:"created-at,omitempty"` + // VPC description + Description string `json:"description,omitempty" validate:"omitempty,lte=4096"` + // VPC ID + ID UUID `json:"id,omitempty"` + Labels Labels `json:"labels,omitempty"` + // VPC name + Name string `json:"name,omitempty" validate:"omitempty,gte=1,lte=255"` +} + +// Live balance +type LiveBalance struct { + // Organization live balance + Balance float64 `json:"balance,omitempty"` + // Organization currency + Currency string `json:"currency,omitempty"` } type LoadBalancerState string @@ -4302,6 +4485,7 @@ type Manager struct { Type ManagerType `json:"type,omitempty"` } +// Model reference. Provide either id or name. type ModelRef struct { // Associated model ID ID UUID `json:"id,omitempty"` @@ -4309,6 +4493,16 @@ type ModelRef struct { Name string `json:"name,omitempty" validate:"omitempty,gte=1"` } +// Accumulated Unit Of Measurement (UOM) counters for one model over a flush window +type ModelUsageCounters struct { + // Number of inference calls in this flush window + CallCount int `json:"call-count" validate:"required,gte=1"` + // Total prompt/input Unit Of Measurement (UOM) across all calls in this flush window (e.g., tokens for LLMs, minutes for TTS, pages for OCR) + InputUom int `json:"input-uom" validate:"required,gte=0"` + // Total completion/output Unit Of Measurement (UOM) across all calls in this flush window (e.g., tokens for LLMs, minutes for TTS, pages for OCR) + OutputUom int `json:"output-uom" validate:"required,gte=0"` +} + // Cluster networking configuration. type Networking struct { // CIDR Range for Pods in cluster. This must not overlap with any IP ranges assigned to pods. Max of two, comma-separated, dual-stack CIDRs is allowed. @@ -4378,11 +4572,17 @@ type OperationResourceRef struct { Link string `json:"link,omitempty"` } +// Per-org Unit Of Measurement (UOM) consumption quota response +type OrgConsumptionQuotaResponse struct { + // Per-org Unit Of Measurement (UOM) consumption quota (UOM/min). Null means unlimited. UOM represents weighted units across different AI workloads (e.g., tokens for LLMs, minutes for TTS, pages for OCR). + QuotaUomPerMinute int `json:"quota-uom-per-minute,omitempty" validate:"omitempty,gte=0"` +} + // Organization type Organization struct { // Organization address Address string `json:"address,omitempty"` - // Organization balance + // Organization balance. DEPRECATED: use the dedicated `live-balance` endpoint Balance float64 `json:"balance,omitempty"` // Organization city City string `json:"city,omitempty"` @@ -4400,8 +4600,18 @@ type Organization struct { // Organization GPU usage type OrganizationUsage struct { - // Total GPU count + // Total GPU count (sum of all GPU types) Gpu int64 `json:"gpu" validate:"required,gte=0"` + // GPU3 count + Gpu3 int64 `json:"gpu3,omitempty" validate:"omitempty,gte=0"` + // GPU3080TI count + Gpu3080ti int64 `json:"gpu3080ti,omitempty" validate:"omitempty,gte=0"` + // GPUA30 count + Gpua30 int64 `json:"gpua30,omitempty" validate:"omitempty,gte=0"` + // GPUA5000 count + Gpua5000 int64 `json:"gpua5000,omitempty" validate:"omitempty,gte=0"` + // GPURTX6000PRO count + Gpurtx6000pro int64 `json:"gpurtx6000pro,omitempty" validate:"omitempty,gte=0"` } // Private Network @@ -4471,6 +4681,14 @@ type Quota struct { Usage int64 `json:"usage,omitempty"` } +// Rate Limit +type RateLimited struct { + // The error message + Error string `json:"error,omitempty"` + // The time in seconds to wait before the next request + RetryAfter float64 `json:"retry_after,omitempty"` +} + type ReEncryptRequestDestination struct { // Optional encryption context appended to the AAD. EncryptionContext *[]byte `json:"encryption-context,omitempty"` @@ -4525,9 +4743,16 @@ type Resource struct { Name string `json:"name,omitempty"` } +// Reveal AI API key response +type RevealAIAPIKeyResponse struct { + // Plaintext AI API key value + Value string `json:"value" validate:"required"` +} + // AI deployment inference endpoint authentication key type RevealDeploymentAPIKeyResponse struct { - APIKey string `json:"api-key,omitempty"` + // Inference endpoint authentication key + APIKey string `json:"api-key" validate:"required"` } type ReverseDNSRecord struct { @@ -4539,6 +4764,12 @@ type RevisionStamp struct { Seq int `json:"seq" validate:"required,gte=0"` } +// Rotate AI API key response +type RotateAIAPIKeyResponse struct { + // Plaintext AI API key value + Value string `json:"value" validate:"required"` +} + type RotateKmsKeyResponse struct { Rotation *KeyRotationConfig `json:"rotation" validate:"required"` } @@ -4640,6 +4871,12 @@ type SecurityGroupRule struct { StartPort int64 `json:"start-port,omitempty" validate:"omitempty,gte=1,lte=65535"` } +// Request to set per-org Unit Of Measurement (UOM) consumption quota +type SetOrgConsumptionQuotaRequest struct { + // Per-org Unit Of Measurement (UOM) consumption quota (UOM/min). Pass null to remove the limit. UOM represents weighted units across different AI workloads (e.g., tokens for LLMs, minutes for TTS, pages for OCR). + QuotaUomPerMinute int `json:"quota-uom-per-minute,omitempty" validate:"omitempty,gte=0"` +} + // Kubernetes Audit parameters type SKSAudit struct { // Enabled @@ -4944,6 +5181,7 @@ type SuccessResponseStatus string const ( SuccessResponseStatusSuccess SuccessResponseStatus = "success" SuccessResponseStatusTargetRegistered SuccessResponseStatus = "target-registered" + SuccessResponseStatusAlreadyApplied SuccessResponseStatus = "already-applied" ) type SuccessResponse struct { @@ -5018,6 +5256,22 @@ type UpdateAIAPIKeyRequest struct { Scope string `json:"scope,omitempty"` } +// Update AI API key response +type UpdateAIAPIKeyResponse struct { + // Creation timestamp + CreatedAT time.Time `json:"created-at" validate:"required"` + // AI API key ID + ID UUID `json:"id" validate:"required"` + // Human-readable name for the AI API key + Name string `json:"name" validate:"required"` + // Organization UUID that owns this key + OrgUuid UUID `json:"org-uuid" validate:"required"` + // Key scope: 'public' for all deployments, or a specific deployment UUID + Scope string `json:"scope" validate:"required"` + // Last update timestamp + UpdatedAT time.Time `json:"updated-at" validate:"required"` +} + // Update AI deployment type UpdateDeploymentRequest struct { // Optional extra inference engine server CLI args @@ -5044,6 +5298,19 @@ type User struct { TwoFactorAuthentication *bool `json:"two-factor-authentication,omitempty"` } +// VPC +type Vpc struct { + // VPC creation date + CreatedAT time.Time `json:"created-at,omitempty"` + // VPC description + Description string `json:"description,omitempty" validate:"omitempty,lte=4096"` + // VPC ID + ID UUID `json:"id,omitempty"` + Labels Labels `json:"labels,omitempty"` + // VPC name + Name string `json:"name,omitempty" validate:"omitempty,gte=1,lte=255"` +} + // Zone type Zone struct { // Zone API endpoint diff --git a/vendor/github.com/exoscale/egoscale/v3/version.go b/vendor/github.com/exoscale/egoscale/v3/version.go index 6611bd70a..2b80947e4 100644 --- a/vendor/github.com/exoscale/egoscale/v3/version.go +++ b/vendor/github.com/exoscale/egoscale/v3/version.go @@ -1,4 +1,4 @@ package v3 // Version represents the current egoscale v3 version. -const Version = "v3.1.36" +const Version = "v3.1.38" diff --git a/vendor/modules.txt b/vendor/modules.txt index e7c5ceb8a..16eb2a538 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -154,8 +154,8 @@ github.com/dlclark/regexp2/syntax # github.com/dustin/go-humanize v1.0.1 ## explicit; go 1.16 github.com/dustin/go-humanize -# github.com/exoscale/egoscale/v3 v3.1.36-0.20260424083744-33446130ebd9 -## explicit; go 1.26 +# github.com/exoscale/egoscale/v3 v3.1.38 +## explicit; go 1.25 github.com/exoscale/egoscale/v3 github.com/exoscale/egoscale/v3/credentials # github.com/exoscale/openapi-cli-generator v1.2.0