diff --git a/CHANGELOG.md b/CHANGELOG.md index a71fa933e9..42029dafa7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ Versioning follows [SemVer](https://semver.org/). Sections: **Added**, **Changed**, **Deprecated**, **Fixed**, **Removed**, **Known Limitations**, **Dependencies**. Only user-visible changes listed. Older entries may use non-standard section names. +## [v6.10.3] - August 2026 + +### Added +- Confidential Computing support: + - `image upload --confidential` uploads to the `/confidential-images/` FTP directory. Restricted to QCOW2 images (which must carry a `LAUNCH_ARTIFACTS` partition), and forces the image's mutable properties to the only values the platform accepts: `cloud-init NONE`, all hot-plug disabled, and legacy BIOS off. Conflicting explicit flags are rejected. + - `server create --confidential` creates a Confidential VM from a confidential boot image. Requires `--type ENTERPRISE` and `--image-id` (a private, SEV-SNP image; `--image-id` tab-completion is filtered to these). A boot volume is built from the image and attached in the same request (size it with `--size`/`--storage-type`), so the API can derive `cores` and `cpuFamily` from the image's `launch-config.json` — `--cores` and `--cpu-family` must not be set. + - `RequiredFeatures` and `EnabledFeatures` columns. + ## [v6.10.2] - June 2026 ### Added diff --git a/commands/compute/image/confidential_test.go b/commands/compute/image/confidential_test.go new file mode 100644 index 0000000000..06d05d3e97 --- /dev/null +++ b/commands/compute/image/confidential_test.go @@ -0,0 +1,124 @@ +package image + +import ( + "bufio" + "bytes" + "testing" + + "github.com/ionos-cloud/ionosctl/v6/internal/constants" + "github.com/ionos-cloud/ionosctl/v6/internal/core" + cloudapiv6 "github.com/ionos-cloud/ionosctl/v6/services/cloudapi-v6" + "github.com/ionos-cloud/ionosctl/v6/services/cloudapi-v6/resources" + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" +) + +func TestForceConfidentialImageProperties(t *testing.T) { + p := resources.ImageProperties{} + forceConfidentialImageProperties(&p) + + assert.Equal(t, "NONE", *p.CloudInit) + assert.False(t, *p.CpuHotPlug) + assert.False(t, *p.RamHotPlug) + assert.False(t, *p.NicHotPlug) + assert.False(t, *p.DiscVirtioHotPlug) + assert.False(t, *p.DiscScsiHotPlug) + assert.False(t, *p.CpuHotUnplug) + assert.False(t, *p.RamHotUnplug) + assert.False(t, *p.NicHotUnplug) + assert.False(t, *p.DiscVirtioHotUnplug) + assert.False(t, *p.DiscScsiHotUnplug) + assert.False(t, *p.RequireLegacyBios) +} + +func TestAllImageColsHasRequiredFeatures(t *testing.T) { + var found bool + for _, c := range allImageCols { + if c.Name == "RequiredFeatures" { + found = true + } + } + assert.True(t, found, "allImageCols must expose a RequiredFeatures column") +} + +// preRunImageUploadConfidential registers the flags PreRunImageUpload touches, applies the given +// setup, and returns the resulting error. +func runPreRunImageUpload(t *testing.T, setup func(cfg *core.PreCommandConfig)) error { + t.Helper() + var b bytes.Buffer + w := bufio.NewWriter(&b) + var got error + core.PreCmdConfigTest(t, w, func(cfg *core.PreCommandConfig) { + viper.Reset() + viper.Set(constants.ArgOutput, constants.DefaultOutputFormat) + + fs := cfg.Command.Command.Flags() + fs.StringSlice(FlagImage, nil, "") + fs.String(constants.FlagCloudInit, "", "") + fs.Bool(cloudapiv6.ArgCpuHotPlug, false, "") + fs.Bool(cloudapiv6.ArgRamHotPlug, false, "") + fs.Bool(cloudapiv6.ArgNicHotPlug, false, "") + fs.Bool(cloudapiv6.ArgDiscVirtioHotPlug, false, "") + fs.Bool(cloudapiv6.ArgDiscScsiHotPlug, false, "") + fs.Bool(cloudapiv6.ArgRequireLegacyBios, false, "") + + setup(cfg) + got = PreRunImageUpload(cfg) + }) + return got +} + +func TestPreRunImageUploadConfidential(t *testing.T) { + t.Run("rejects non-qcow2", func(t *testing.T) { + err := runPreRunImageUpload(t, func(cfg *core.PreCommandConfig) { + viper.Set(core.GetFlagName(cfg.NS, constants.FlagConfidential), true) + viper.Set(core.GetFlagName(cfg.NS, FlagImage), []string{"disk.iso"}) + }) + assert.Error(t, err) + }) + + t.Run("rejects cloud-init V1", func(t *testing.T) { + err := runPreRunImageUpload(t, func(cfg *core.PreCommandConfig) { + viper.Set(core.GetFlagName(cfg.NS, constants.FlagConfidential), true) + viper.Set(core.GetFlagName(cfg.NS, FlagImage), []string{"disk.qcow2"}) + viper.Set(core.GetFlagName(cfg.NS, constants.FlagCloudInit), "V1") + _ = cfg.Command.Command.Flags().Set(constants.FlagCloudInit, "V1") + }) + assert.Error(t, err) + }) + + t.Run("rejects hot-plug enabled", func(t *testing.T) { + err := runPreRunImageUpload(t, func(cfg *core.PreCommandConfig) { + viper.Set(core.GetFlagName(cfg.NS, constants.FlagConfidential), true) + viper.Set(core.GetFlagName(cfg.NS, FlagImage), []string{"disk.qcow2"}) + viper.Set(core.GetFlagName(cfg.NS, cloudapiv6.ArgCpuHotPlug), true) + _ = cfg.Command.Command.Flags().Set(cloudapiv6.ArgCpuHotPlug, "true") + }) + assert.Error(t, err) + }) + + t.Run("rejects require-legacy-bios true", func(t *testing.T) { + err := runPreRunImageUpload(t, func(cfg *core.PreCommandConfig) { + viper.Set(core.GetFlagName(cfg.NS, constants.FlagConfidential), true) + viper.Set(core.GetFlagName(cfg.NS, FlagImage), []string{"disk.qcow2"}) + viper.Set(core.GetFlagName(cfg.NS, cloudapiv6.ArgRequireLegacyBios), true) + _ = cfg.Command.Command.Flags().Set(cloudapiv6.ArgRequireLegacyBios, "true") + }) + assert.Error(t, err) + }) + + t.Run("accepts clean qcow2", func(t *testing.T) { + err := runPreRunImageUpload(t, func(cfg *core.PreCommandConfig) { + viper.Set(core.GetFlagName(cfg.NS, constants.FlagConfidential), true) + viper.Set(core.GetFlagName(cfg.NS, FlagImage), []string{"disk.qcow2"}) + }) + assert.NoError(t, err) + }) + + t.Run("no restriction without --confidential", func(t *testing.T) { + err := runPreRunImageUpload(t, func(cfg *core.PreCommandConfig) { + viper.Set(core.GetFlagName(cfg.NS, FlagImage), []string{"disk.iso"}) + }) + assert.NoError(t, err) + }) +} diff --git a/commands/compute/image/image.go b/commands/compute/image/image.go index 97579a58f5..a47df3c9d0 100644 --- a/commands/compute/image/image.go +++ b/commands/compute/image/image.go @@ -24,6 +24,7 @@ var allImageCols = []table.Column{ {Name: "ExposeSerial", JSONPath: "properties.exposeSerial"}, {Name: "RequireLegacyBios", JSONPath: "properties.requireLegacyBios"}, {Name: "ApplicationType", JSONPath: "properties.applicationType"}, + {Name: "RequiredFeatures", JSONPath: "properties.requiredFeatures"}, } func ImageCmd() *core.Command { diff --git a/commands/compute/image/upload.go b/commands/compute/image/upload.go index 038bf7998b..b7597ba70b 100644 --- a/commands/compute/image/upload.go +++ b/commands/compute/image/upload.go @@ -163,7 +163,7 @@ High level steps: 4. Print the resulting image objects to stdout in the chosen table or JSON format. AUTH AND SAFETY - - The FTP server relies on API credentials via environment variables IONOS_USERNAME and IONOS_PASSWORD. You can debug your current setup with "ionosctl whoami --provenance". + - The FTP server relies on basic API credentials via environment variables IONOS_USERNAME and IONOS_PASSWORD. A bearer token (IONOS_TOKEN) cannot be used for the FTP upload, so if you authenticate with a token you must additionally set IONOS_USERNAME and IONOS_PASSWORD (they may be set alongside IONOS_TOKEN). You can debug your current setup with "ionosctl whoami --provenance". - Use --skip-update to skip the API PATCH step if you only want to perform an FTP upload and not modify images through the API. - Use --skip-verify to skip verifying the FTP server certificate. Only use that for trusted servers. Skipping certificate verification can expose you to man-in-the-middle attacks. - If using a custom FTP server it is advised to use a self-signed certificate instead of --skip-verify. Provide its PEM file via --crt-path. The file should contain the server certificate in base64 PEM format. @@ -225,6 +225,8 @@ EXAMPLES upload.AddIntFlag(FlagFtpPort, "", 21, "FTP server port. Only valid together with --ftp-url, for custom FTP servers on non-standard ports") + upload.AddBoolFlag(constants.FlagConfidential, "", false, "Upload to the confidential-images/ directory for Confidential Computing (CoCo) images. Requires a QCOW2 image with an embedded LAUNCH_ARTIFACTS partition. Forces cloud-init NONE and disables hot-plug / legacy BIOS on the image.") + addPropertiesFlags(upload) upload.Command.Flags().SortFlags = false // Hot Plugs generate a lot of flags to scroll through, put them at the end @@ -233,6 +235,67 @@ EXAMPLES return upload } +// forceConfidentialImageProperties sets the only image properties the API accepts for a +// Confidential Computing image: cloud-init NONE, all hot-plug/hot-unplug disabled, and no legacy BIOS. +func forceConfidentialImageProperties(p *resources.ImageProperties) { + p.SetCloudInit("NONE") + p.SetCpuHotPlug(false) + p.SetRamHotPlug(false) + p.SetNicHotPlug(false) + p.SetDiscVirtioHotPlug(false) + p.SetDiscScsiHotPlug(false) + p.SetCpuHotUnplug(false) + p.SetRamHotUnplug(false) + p.SetNicHotUnplug(false) + p.SetDiscVirtioHotUnplug(false) + p.SetDiscScsiHotUnplug(false) + p.SetRequireLegacyBios(false) +} + +// resolveFTPCredentials returns the username/password to use for the FTP upload. The FTP server +// cannot authenticate with a bearer token, so a token-authenticated client (IONOS_TOKEN set, +// username/password empty) would send an empty USER and fail with a 500. Fall back to the +// IONOS_USERNAME / IONOS_PASSWORD env vars, then to the current config profile's basic credentials, +// so token users can still upload by additionally providing basic credentials. +func resolveFTPCredentials() (string, string, error) { + cfg := client.Must().CloudClient.GetConfig() + + var profUser, profPass string + if prof := client.Must().Config.GetCurrentProfile(); prof != nil { + profUser, profPass = prof.Credentials.Username, prof.Credentials.Password + } + + user, pass, ok := pickFTPCredentials( + cfg.Username, cfg.Password, + os.Getenv(constants.EnvUsername), os.Getenv(constants.EnvPassword), + profUser, profPass, + ) + if !ok { + return "", "", fmt.Errorf("FTP upload requires basic credentials (username & password); "+ + "the FTP server does not accept a bearer token. Set %s and %s (they can be set alongside %s)", + constants.EnvUsername, constants.EnvPassword, constants.EnvToken) + } + + return user, pass, nil +} + +// pickFTPCredentials chooses the first source that provides a complete username+password pair, in +// precedence order: the client's loaded basic credentials, then the IONOS_USERNAME/IONOS_PASSWORD +// env vars, then the current config profile. Returns ok=false when no source is complete — which is +// exactly the token-only case, since FTP cannot use a bearer token. +func pickFTPCredentials(cfgUser, cfgPass, envUser, envPass, profUser, profPass string) (string, string, bool) { + for _, pair := range [][2]string{ + {cfgUser, cfgPass}, + {envUser, envPass}, + {profUser, profPass}, + } { + if pair[0] != "" && pair[1] != "" { + return pair[0], pair[1], true + } + } + return "", "", false +} + func updateImagesAfterUpload(c *core.CommandConfig, diffImgs []ionoscloud.Image, properties resources.ImageProperties) ([]ionoscloud.Image, error) { // do a patch on the uploaded images var imgs []ionoscloud.Image @@ -257,8 +320,17 @@ func RunImageUpload(c *core.CommandConfig) error { aliases := viper.GetStringSlice(core.GetFlagName(c.NS, cloudapiv6.ArgImageAlias)) locations := viper.GetStringSlice(core.GetFlagName(c.NS, cloudapiv6.ArgLocation)) skipVerify := viper.GetBool(core.GetFlagName(c.NS, FlagSkipVerify)) + confidential := viper.GetBool(core.GetFlagName(c.NS, constants.FlagConfidential)) - ctx, cancel := context.WithTimeout(c.Context, time.Duration(viper.GetInt(core.GetFlagName(c.NS, constants.ArgTimeout)))*time.Second) + ftpUser, ftpPass, err := resolveFTPCredentials() + if err != nil { + return err + } + + // --timeout is a global persistent flag bound to viper's flat "timeout" key (see commands/root.go). + // It must be read by that flat key, not the namespaced one, otherwise it reads 0 and the context + // expires immediately, surfacing as a misleading "i/o timeout" on the first FTP dial. + ctx, cancel := context.WithTimeout(c.Context, time.Duration(viper.GetInt(constants.ArgTimeout))*time.Second) defer cancel() c.Context = ctx @@ -289,14 +361,17 @@ func RunImageUpload(c *core.CommandConfig) error { } c.Verbose("Uploading %s to %s", img, url) - var isoOrHdd string - if ext := filepath.Ext(img); ext == ".iso" || ext == ".img" { - isoOrHdd = "iso" - } else { - isoOrHdd = "hdd" + var serverDir string + switch { + case confidential: + serverDir = "confidential-images/" + case filepath.Ext(img) == ".iso" || filepath.Ext(img) == ".img": + serverDir = "iso-images/" + default: + serverDir = "hdd-images/" } - serverFilePath := fmt.Sprintf("%s-images/", isoOrHdd) // iso-images / hdd-images + serverFilePath := serverDir if len(aliases) == 0 { serverFilePath += filepath.Base(img) // If no custom alias, use the filename } else { @@ -319,8 +394,8 @@ func RunImageUpload(c *core.CommandConfig) error { Port: viper.GetInt(core.GetFlagName(c.NS, FlagFtpPort)), SkipVerify: skipVerify, ServerCertificate: certPool, - Username: client.Must().CloudClient.GetConfig().Username, - Password: client.Must().CloudClient.GetConfig().Password, + Username: ftpUser, + Password: ftpPass, }, ImageFileProperties: resources.ImageFileProperties{ Path: serverFilePath, @@ -362,6 +437,13 @@ func RunImageUpload(c *core.CommandConfig) error { } properties := getDesiredImageAfterPatch(c, true) + if confidential { + // Confidential images have a fixed, restricted property set: the API rejects cloud-init V1, + // any hot-plug, and legacy BIOS. getDesiredImageAfterPatch sends flag defaults (cloud-init V1, + // hot-plug true), so override them to the only values the API accepts. Conflicting explicit + // flags are already rejected in PreRunImageUpload. + forceConfidentialImageProperties(&properties) + } imgs, err := updateImagesAfterUpload(c, diffImgs, properties) if err != nil { return fmt.Errorf("failed updating image with given properties, but uploading to FTP successful: %w", err) @@ -441,6 +523,35 @@ func PreRunImageUpload(c *core.PreCommandConfig) error { return fmt.Errorf("%s is an invalid image extension. Valid extensions are: %s", strings.Join(invalidImages, ","), validExts) } + // Confidential Computing images must be QCOW2 and carry a fixed, restricted property set. + // Reject conflicting explicit flags up front rather than silently overriding a user's choice. + if viper.GetBool(core.GetFlagName(c.NS, constants.FlagConfidential)) { + for _, img := range images { + if ext := filepath.Ext(img); ext != ".qcow2" && ext != ".qcow" { + return fmt.Errorf("--%s requires QCOW2 images; %s has extension %q", constants.FlagConfidential, img, ext) + } + } + + changed := c.Command.Command.Flags().Changed + if changed(constants.FlagCloudInit) && + strings.EqualFold(viper.GetString(core.GetFlagName(c.NS, constants.FlagCloudInit)), "V1") { + return fmt.Errorf("--%s images require cloud-init NONE; do not pass --%s V1", constants.FlagConfidential, constants.FlagCloudInit) + } + for _, f := range []string{ + cloudapiv6.ArgCpuHotPlug, cloudapiv6.ArgRamHotPlug, cloudapiv6.ArgNicHotPlug, + cloudapiv6.ArgDiscVirtioHotPlug, cloudapiv6.ArgDiscScsiHotPlug, + cloudapiv6.ArgCpuHotUnplug, cloudapiv6.ArgRamHotUnplug, cloudapiv6.ArgNicHotUnplug, + cloudapiv6.ArgDiscVirtioHotUnplug, cloudapiv6.ArgDiscScsiHotUnplug, + } { + if changed(f) && viper.GetBool(core.GetFlagName(c.NS, f)) { + return fmt.Errorf("--%s images cannot enable hot-plug/hot-unplug; do not pass --%s", constants.FlagConfidential, f) + } + } + if changed(cloudapiv6.ArgRequireLegacyBios) && viper.GetBool(core.GetFlagName(c.NS, cloudapiv6.ArgRequireLegacyBios)) { + return fmt.Errorf("--%s images require --%s=false", constants.FlagConfidential, cloudapiv6.ArgRequireLegacyBios) + } + } + // --ftp-port only makes sense with a custom --ftp-url if viper.IsSet(core.GetFlagName(c.NS, FlagFtpPort)) { if !viper.IsSet(core.GetFlagName(c.NS, FlagFtpUrl)) { diff --git a/commands/compute/image/upload_test.go b/commands/compute/image/upload_test.go index 28b7e16f62..0cd750d4ca 100644 --- a/commands/compute/image/upload_test.go +++ b/commands/compute/image/upload_test.go @@ -60,6 +60,45 @@ func TestDeduplicateLocations(t *testing.T) { } } +func TestPickFTPCredentials(t *testing.T) { + tests := []struct { + name string + cfgUser, cfgPass, envUser, envPass, profUser, profPass string + wantUser, wantPass string + wantOK bool + }{ + { + name: "client basic creds win", cfgUser: "cfg", cfgPass: "cfgpw", + envUser: "env", envPass: "envpw", wantUser: "cfg", wantPass: "cfgpw", wantOK: true, + }, + { + // token-only client: cfg user/pass empty -> fall back to env (the reported scenario). + name: "env fallback when client has token only", envUser: "env", envPass: "envpw", + wantUser: "env", wantPass: "envpw", wantOK: true, + }, + { + name: "profile fallback when env incomplete", envUser: "env", // envPass missing + profUser: "prof", profPass: "profpw", wantUser: "prof", wantPass: "profpw", wantOK: true, + }, + { + // pure token-only, nothing else set: FTP cannot authenticate. + name: "incomplete pair -> not ok", cfgUser: "onlyuser", wantOK: false, + }, + { + name: "all empty -> not ok", wantOK: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + u, p, ok := pickFTPCredentials(tt.cfgUser, tt.cfgPass, tt.envUser, tt.envPass, tt.profUser, tt.profPass) + assert.Equal(t, tt.wantOK, ok) + assert.Equal(t, tt.wantUser, u) + assert.Equal(t, tt.wantPass, p) + }) + } +} + func TestLookupAPI(t *testing.T) { tests := []struct { input string diff --git a/commands/compute/location/confidential_test.go b/commands/compute/location/confidential_test.go new file mode 100644 index 0000000000..cb04ee2445 --- /dev/null +++ b/commands/compute/location/confidential_test.go @@ -0,0 +1,25 @@ +package location + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLocationColsHaveEnabledFeatures(t *testing.T) { + var cpuHas bool + for _, c := range allCpuCols { + if c.Name == "EnabledFeatures" { + cpuHas = true + } + } + assert.True(t, cpuHas, "allCpuCols must expose an EnabledFeatures column (CoCo CPU-family discovery)") + + var locHas bool + for _, c := range allLocationCols { + if c.Name == "CpuEnabledFeatures" { + locHas = true + } + } + assert.True(t, locHas, "allLocationCols must expose a CpuEnabledFeatures column") +} diff --git a/commands/compute/location/cpu.go b/commands/compute/location/cpu.go index 522a190146..d49d061e3a 100644 --- a/commands/compute/location/cpu.go +++ b/commands/compute/location/cpu.go @@ -16,6 +16,7 @@ var allCpuCols = []table.Column{ {Name: "MaxCores", JSONPath: "maxCores", Default: true}, {Name: "MaxRam", JSONPath: "maxRam", Default: true}, {Name: "Vendor", JSONPath: "vendor", Default: true}, + {Name: "EnabledFeatures", JSONPath: "enabledFeatures", Default: true}, } func CpuCmd() *core.Command { diff --git a/commands/compute/location/location.go b/commands/compute/location/location.go index 27d5e7b56a..87e8ad5ab5 100644 --- a/commands/compute/location/location.go +++ b/commands/compute/location/location.go @@ -13,6 +13,7 @@ var allLocationCols = []table.Column{ {Name: "Features", JSONPath: "properties.features"}, {Name: "ImageAliases", JSONPath: "properties.imageAliases"}, {Name: "CpuFamily", JSONPath: "properties.cpuArchitecture.*.cpuFamily", Default: true}, + {Name: "CpuEnabledFeatures", JSONPath: "properties.cpuArchitecture.*.enabledFeatures"}, } func LocationCmd() *core.Command { diff --git a/commands/compute/request/request_test.go b/commands/compute/request/request_test.go index 0cf2713ee7..f2212ac8c6 100644 --- a/commands/compute/request/request_test.go +++ b/commands/compute/request/request_test.go @@ -305,6 +305,34 @@ func TestRunRequestWait(t *testing.T) { }) } +// TestRunRequestWaitHonorsTimeout is a regression test for the bug where +// RunRequestWait read the timeout from a namespaced viper key (always 0), +// producing an already-expired context. --timeout is a global persistent flag +// bound to the flat "timeout" viper key, so it must be read by that key. +func TestRunRequestWaitHonorsTimeout(t *testing.T) { + var b bytes.Buffer + w := bufio.NewWriter(&b) + core.CmdConfigTest(t, w, func(cfg *core.CommandConfig, rm *core.ResourcesMocksTest) { + viper.Reset() + viper.Set(constants.ArgOutput, constants.DefaultOutputFormat) + viper.Set(constants.ArgQuiet, false) + viper.Set(core.GetFlagName(cfg.NS, cloudapiv6.ArgRequestId), testRequestVar) + viper.Set(constants.ArgTimeout, 600) // flat key, as the persistent flag binds it + req := resources.Request{Request: rq} + rm.CloudApiV6Mocks.Request.EXPECT().Get(testRequestVar).Return(&req, nil, nil) + rm.CloudApiV6Mocks.Request.EXPECT().Wait(testRequestPathVar+"/status"). + DoAndReturn(func(path string) (*resources.Response, error) { + dl, ok := cfg.Context.Deadline() + assert.True(t, ok, "wait context must carry a deadline") + assert.Greater(t, time.Until(dl), 500*time.Second, + "timeout must be honored (~600s); a near-zero deadline means the flag was read from the wrong key") + return nil, nil + }) + err := RunRequestWait(cfg) + assert.NoError(t, err) + }) +} + func TestRunRequestWaitErr(t *testing.T) { var b bytes.Buffer w := bufio.NewWriter(&b) diff --git a/commands/compute/request/run_request.go b/commands/compute/request/run_request.go index c14f3933ed..a917c91882 100644 --- a/commands/compute/request/run_request.go +++ b/commands/compute/request/run_request.go @@ -84,8 +84,10 @@ func RunRequestWait(c *core.CommandConfig) error { return err } - // Default timeout: 60s - timeout := viper.GetInt(core.GetFlagName(c.NS, constants.ArgTimeout)) + // --timeout is a global persistent flag bound to viper's flat "timeout" key (see commands/root.go), + // so it must be read by that flat key. Reading the namespaced key returned 0, which made the + // context expire immediately and the wait fail instantly. Default is 600s (the flag's default). + timeout := viper.GetInt(constants.ArgTimeout) ctxTimeout, cancel := context.WithTimeout( c.Context, time.Duration(timeout)*time.Second, diff --git a/commands/compute/server/confidential_test.go b/commands/compute/server/confidential_test.go new file mode 100644 index 0000000000..978e987f6e --- /dev/null +++ b/commands/compute/server/confidential_test.go @@ -0,0 +1,157 @@ +package server + +import ( + "bufio" + "bytes" + "strconv" + "testing" + + "github.com/golang/mock/gomock" + "github.com/ionos-cloud/ionosctl/v6/commands/compute/testutil" + "github.com/ionos-cloud/ionosctl/v6/internal/constants" + "github.com/ionos-cloud/ionosctl/v6/internal/core" + cloudapiv6 "github.com/ionos-cloud/ionosctl/v6/services/cloudapi-v6" + "github.com/ionos-cloud/ionosctl/v6/services/cloudapi-v6/resources" + ionoscloud "github.com/ionos-cloud/sdk-go/v6" + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" +) + +func TestAllServerColsHasEnabledFeatures(t *testing.T) { + var found bool + for _, c := range AllServerCols { + if c.Name == "EnabledFeatures" { + found = true + } + } + assert.True(t, found, "AllServerCols must expose an EnabledFeatures column") +} + +// getNewServer must not set cores/cpuFamily for a Confidential VM — both are derived from the image. +func TestGetNewServerConfidential(t *testing.T) { + var b bytes.Buffer + w := bufio.NewWriter(&b) + core.CmdConfigTest(t, w, func(cfg *core.CommandConfig, rm *core.ResourcesMocksTest) { + viper.Reset() + viper.Set(constants.ArgOutput, constants.DefaultOutputFormat) + viper.Set(core.GetFlagName(cfg.NS, constants.FlagType), testServerEnterpriseType) + viper.Set(core.GetFlagName(cfg.NS, constants.FlagConfidential), true) + viper.Set(core.GetFlagName(cfg.NS, cloudapiv6.ArgName), testServerVar) + viper.Set(core.GetFlagName(cfg.NS, constants.FlagRam), strconv.Itoa(int(ram))) + // Set cores/cpu-family in viper to prove getNewServer IGNORES them when confidential. + viper.Set(core.GetFlagName(cfg.NS, constants.FlagCores), cores) + viper.Set(core.GetFlagName(cfg.NS, constants.FlagCpuFamily), testServerVar) + + srv, err := getNewServer(cfg) + assert.NoError(t, err) + assert.Equal(t, testServerEnterpriseType, *srv.Properties.Type) + assert.Nil(t, srv.Properties.Cores, "cores must be derived from the image") + assert.Nil(t, srv.Properties.CpuFamily, "cpuFamily must be derived from the image") + }) +} + +// getNewDAS builds the confidential boot volume with the requested storage type and size. +func TestGetNewDASConfidential(t *testing.T) { + var b bytes.Buffer + w := bufio.NewWriter(&b) + core.CmdConfigTest(t, w, func(cfg *core.CommandConfig, rm *core.ResourcesMocksTest) { + viper.Reset() + viper.Set(constants.ArgOutput, constants.DefaultOutputFormat) + viper.Set(core.GetFlagName(cfg.NS, constants.FlagType), testServerEnterpriseType) + viper.Set(core.GetFlagName(cfg.NS, constants.FlagConfidential), true) + viper.Set(core.GetFlagName(cfg.NS, constants.FlagStorageType), "SSD") + viper.Set(core.GetFlagName(cfg.NS, cloudapiv6.ArgSize), "20") + viper.Set(core.GetFlagName(cfg.NS, cloudapiv6.ArgImageId), testServerVar) + viper.Set(core.GetFlagName(cfg.NS, cloudapiv6.ArgVolumeName), testServerVar) + viper.Set(core.GetFlagName(cfg.NS, cloudapiv6.ArgBus), "VIRTIO") + + vol, err := getNewDAS(cfg) + assert.NoError(t, err) + assert.Equal(t, "SSD", *vol.Properties.Type) + assert.Equal(t, float32(20), *vol.Properties.Size) + assert.Equal(t, testServerVar, *vol.Properties.Image) + }) +} + +func TestPreRunServerCreateConfidentialErrors(t *testing.T) { + tests := []struct { + name string + serverType string + setCores bool + setCpuFamily bool + setImage bool + }{ + {name: "non-enterprise type", serverType: serverVCPUType, setImage: true}, + {name: "cores set", serverType: serverEnterpriseType, setCores: true, setImage: true}, + {name: "cpu-family set", serverType: serverEnterpriseType, setCpuFamily: true, setImage: true}, + {name: "missing image", serverType: serverEnterpriseType}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var b bytes.Buffer + w := bufio.NewWriter(&b) + core.PreCmdConfigTest(t, w, func(cfg *core.PreCommandConfig) { + viper.Reset() + viper.Set(constants.ArgOutput, constants.DefaultOutputFormat) + + fs := cfg.Command.Command.Flags() + fs.Int(constants.FlagCores, 0, "") + fs.String(constants.FlagCpuFamily, "", "") + fs.String(cloudapiv6.ArgImageId, "", "") + + viper.Set(core.GetFlagName(cfg.NS, constants.FlagConfidential), true) + viper.Set(core.GetFlagName(cfg.NS, constants.FlagType), tt.serverType) + + if tt.setCores { + _ = fs.Set(constants.FlagCores, "4") + } + if tt.setCpuFamily { + _ = fs.Set(constants.FlagCpuFamily, "INTEL_ICELAKE") + } + if tt.setImage { + _ = fs.Set(cloudapiv6.ArgImageId, testServerVar) + } + + err := PreRunServerCreate(cfg) + assert.Error(t, err) + }) + }) + } +} + +// RunServerCreate must attach a boot volume built from the confidential image, and must not set +// cores/cpuFamily (the API derives them from the image). +func TestRunServerCreateConfidentialAttachesVolume(t *testing.T) { + var b bytes.Buffer + w := bufio.NewWriter(&b) + core.CmdConfigTest(t, w, func(cfg *core.CommandConfig, rm *core.ResourcesMocksTest) { + viper.Reset() + viper.Set(constants.ArgOutput, constants.DefaultOutputFormat) + viper.Set(constants.ArgQuiet, false) + viper.Set(core.GetFlagName(cfg.NS, cloudapiv6.ArgDataCenterId), testServerVar) + viper.Set(core.GetFlagName(cfg.NS, cloudapiv6.ArgName), testServerVar) + viper.Set(core.GetFlagName(cfg.NS, constants.FlagType), testServerEnterpriseType) + viper.Set(core.GetFlagName(cfg.NS, constants.FlagConfidential), true) + viper.Set(core.GetFlagName(cfg.NS, cloudapiv6.ArgImageId), testServerVar) + viper.Set(core.GetFlagName(cfg.NS, cloudapiv6.ArgVolumeName), testServerVar) + viper.Set(core.GetFlagName(cfg.NS, constants.FlagStorageType), "HDD") + viper.Set(core.GetFlagName(cfg.NS, cloudapiv6.ArgSize), "10") + viper.Set(core.GetFlagName(cfg.NS, constants.FlagRam), strconv.Itoa(int(ram))) + viper.Set(constants.ArgWait, false) + + rm.CloudApiV6Mocks.Server.EXPECT().Create(testServerVar, gomock.Any()).DoAndReturn( + func(dcId string, input resources.Server) (*resources.Server, *resources.Response, error) { + assert.Nil(t, input.Properties.Cores, "cores must not be set for confidential") + assert.Nil(t, input.Properties.CpuFamily, "cpuFamily must not be set for confidential") + if assert.NotNil(t, input.Entities) && assert.NotNil(t, input.Entities.Volumes) { + items := *input.Entities.Volumes.Items + assert.Len(t, items, 1) + assert.Equal(t, testServerVar, *items[0].Properties.Image) + } + return &resources.Server{Server: ionoscloud.Server{Properties: input.Properties}}, &testutil.TestResponse, nil + }) + + err := RunServerCreate(cfg) + assert.NoError(t, err) + }) +} diff --git a/commands/compute/server/create.go b/commands/compute/server/create.go index 5f62549b24..3c16953aec 100644 --- a/commands/compute/server/create.go +++ b/commands/compute/server/create.go @@ -2,6 +2,7 @@ package server import ( "context" + "strconv" "github.com/ionos-cloud/ionosctl/v6/commands/compute/completer" "github.com/ionos-cloud/ionosctl/v6/internal/client" @@ -101,6 +102,12 @@ Use ` + "`" + `--wait` + "`" + ` (` + "`" + `-w` + "`" + `) to wait for the reso datacenterId := viper.GetString(core.GetFlagName(create.NS, cloudapiv6.ArgDataCenterId)) return completer.DatacenterCPUFamilies(create.Command.Context(), datacenterId), cobra.ShellCompDirectiveNoFileComp }) + create.AddBoolFlag(constants.FlagConfidential, "", false, + "Create a Confidential Computing (SEV-SNP) VM from a confidential boot image. Requires --type ENTERPRISE and --image-id (a private, SEV-SNP image). "+ + "Do not set --cores or --cpu-family: both are derived from the image's launch-config.json. "+ + "A boot volume is created from --image-id and attached automatically; size it with --size and --storage-type.") + create.AddStringFlag(cloudapiv6.ArgSize, "", strconv.Itoa(cloudapiv6.DefaultVolumeSize), "[Confidential] Size of the confidential boot volume, e.g. --size 10 or --size 10GB") + create.AddSetFlag(constants.FlagStorageType, "", "HDD", []string{"HDD", "SSD", "SSD Standard", "SSD Premium"}, "[Confidential] Storage type of the confidential boot volume") create.AddBoolFlag(constants.FlagNICMultiQueue, "", false, constants.FlagNICMultiQueueDescription) create.AddStringFlag(constants.FlagAvailabilityZone, constants.FlagAvailabilityZoneShort, "AUTO", "Availability zone of the Server") _ = create.Command.RegisterFlagCompletionFunc(constants.FlagAvailabilityZone, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { @@ -126,6 +133,7 @@ Use ` + "`" + `--wait` + "`" + ` (` + "`" + `-w` + "`" + `) to wait for the reso create.AddStringFlag(cloudapiv6.ArgImageAlias, cloudapiv6.ArgImageAliasShort, "", "[CUBE Server] The Image Alias to use instead of Image Id for the Direct Attached Storage") create.AddUUIDFlag(cloudapiv6.ArgImageId, "", "", "[CUBE Server] The Image Id or snapshot Id to be used as for the Direct Attached Storage") _ = create.Command.RegisterFlagCompletionFunc(cloudapiv6.ArgImageId, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + confidential := viper.GetBool(core.GetFlagName(create.NS, constants.FlagConfidential)) imageIds := completer.ImageIds(func(r ionoscloud.ApiImagesGetRequest) ionoscloud.ApiImagesGetRequest { // Completer for HDD images that are in the same location as the datacenter chosenDc, _, err := client.Must().CloudClient.DataCentersApi.DatacentersFindById(context.Background(), @@ -134,9 +142,19 @@ Use ` + "`" + `--wait` + "`" + ` (` + "`" + `-w` + "`" + `) to wait for the reso return ionoscloud.ApiImagesGetRequest{} } - return r.Filter("location", *chosenDc.Properties.Location).Filter("imageType", "HDD") + r = r.Filter("location", *chosenDc.Properties.Location).Filter("imageType", "HDD") + if confidential { + // Only private, Confidential-Computing-capable images can boot a Confidential VM. + r = r.Filter("public", "false").Filter("requiredFeatures", "SEV-SNP") + } + return r }) + if confidential { + // Snapshots can't be confidential boot images; don't offer them. + return imageIds, cobra.ShellCompDirectiveNoFileComp + } + snapshotIds := completer.SnapshotIds() return append(imageIds, snapshotIds...), cobra.ShellCompDirectiveNoFileComp diff --git a/commands/compute/server/run_server.go b/commands/compute/server/run_server.go index dce379519c..67785b15f3 100644 --- a/commands/compute/server/run_server.go +++ b/commands/compute/server/run_server.go @@ -40,6 +40,33 @@ func PreRunServerCreate(c *core.PreCommandConfig) error { return err } + // Confidential VMs are ENTERPRISE-only, and their cores + CPU family are derived from the boot + // image's launch-config.json — the API rejects them on the request, so they must not be set here. + if viper.GetBool(core.GetFlagName(c.NS, constants.FlagConfidential)) { + if serverType != serverEnterpriseType { + return fmt.Errorf("--%s requires --%s %s (Confidential VMs are ENTERPRISE-only)", + constants.FlagConfidential, constants.FlagType, serverEnterpriseType) + } + changed := c.Command.Command.Flags().Changed + if changed(constants.FlagCores) || changed(constants.FlagCpuFamily) { + return fmt.Errorf("--%s: do not set --%s or --%s; both are derived from the confidential image's launch-config.json", + constants.FlagConfidential, constants.FlagCores, constants.FlagCpuFamily) + } + if !changed(cloudapiv6.ArgImageId) { + return fmt.Errorf("--%s requires --%s: a Confidential VM must boot from a confidential image "+ + "(find one with: ionosctl image list -F public=false,requiredFeatures=SEV-SNP)", + constants.FlagConfidential, cloudapiv6.ArgImageId) + } + // cores is image-derived here, so drop it from the ENTERPRISE required set. + filtered := make([]string, 0, len(requiredFlags)) + for _, f := range requiredFlags { + if f != constants.FlagCores { + filtered = append(filtered, f) + } + } + requiredFlags = filtered + } + if err = core.CheckRequiredFlags(c.Command, c.NS, requiredFlags...); err != nil { return fmt.Errorf("missing %s flags: %w", serverType, err) } @@ -224,6 +251,21 @@ func RunServerCreate(c *core.CommandConfig) error { }) } + // A Confidential VM must be created together with a boot volume built from the confidential + // image, in the same request — the API derives cores + CPU family from that image. + if viper.GetBool(core.GetFlagName(c.NS, constants.FlagConfidential)) { + volumeConf, err := getNewDAS(c) + if err != nil { + return err + } + + input.SetEntities(ionoscloud.ServerEntities{ + Volumes: &ionoscloud.AttachedVolumes{ + Items: &[]ionoscloud.Volume{volumeConf.Volume}, + }, + }) + } + svr, resp, err := c.CloudApiV6Services.Servers().Create(viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgDataCenterId)), *input) if resp != nil && request.GetId(resp) != "" { c.Verbose(constants.MessageRequestInfo, request.GetId(resp), resp.RequestTime) @@ -576,6 +618,9 @@ func getNewServer(c *core.CommandConfig) (*resources.Server, error) { input.SetAvailabilityZone(availabilityZone) input.SetName(name) + // Confidential VMs derive cores + CPU family from the boot image; leave both unset. + confidential := viper.GetBool(core.GetFlagName(c.NS, constants.FlagConfidential)) + if fn := core.GetFlagName(c.NS, constants.FlagNICMultiQueue); viper.IsSet(fn) { input.SetNicMultiQueue(viper.GetBool(fn)) @@ -631,23 +676,28 @@ func getNewServer(c *core.CommandConfig) (*resources.Server, error) { // ENTERPRISE Server Properties if viper.GetString(core.GetFlagName(c.NS, constants.FlagType)) == serverEnterpriseType { - if viper.IsSet(core.GetFlagName(c.NS, constants.FlagCpuFamily)) && - viper.GetString(core.GetFlagName(c.NS, constants.FlagCpuFamily)) != cloudapiv6.DefaultServerCPUFamily { - input.SetCpuFamily(viper.GetString(core.GetFlagName(c.NS, constants.FlagCpuFamily))) - } else { - cpuFamily, err := DefaultCpuFamily(c) - if err != nil { - return nil, err - } + // For Confidential VMs the CPU family is derived from the image (launch-config vcpu-model); + // leave it unset so the API resolves it. Otherwise use the flag value or the location default. + if !confidential { + if viper.IsSet(core.GetFlagName(c.NS, constants.FlagCpuFamily)) && + viper.GetString(core.GetFlagName(c.NS, constants.FlagCpuFamily)) != cloudapiv6.DefaultServerCPUFamily { + input.SetCpuFamily(viper.GetString(core.GetFlagName(c.NS, constants.FlagCpuFamily))) + } else { + cpuFamily, err := DefaultCpuFamily(c) + if err != nil { + return nil, err + } - input.SetCpuFamily(cpuFamily) + input.SetCpuFamily(cpuFamily) + } } if !input.HasName() { input.SetName("Unnamed Server") } - if viper.IsSet(core.GetFlagName(c.NS, constants.FlagCores)) { + // Cores are derived from the image (launch-config vcpu-count) for Confidential VMs. + if !confidential && viper.IsSet(core.GetFlagName(c.NS, constants.FlagCores)) { cores := viper.GetInt32(core.GetFlagName(c.NS, constants.FlagCores)) input.SetCores(cores) @@ -720,6 +770,21 @@ func getNewDAS(c *core.CommandConfig) (*resources.Volume, error) { if serverType == serverCubeType { volumeProper.SetType("DAS") } + + // Confidential boot volume: a normal sized volume (not template-based DAS) built from the + // confidential image. Set its storage type and size from the dedicated flags. + if viper.GetBool(core.GetFlagName(c.NS, constants.FlagConfidential)) { + volumeProper.SetType(viper.GetString(core.GetFlagName(c.NS, constants.FlagStorageType))) + size, err := utils2.ConvertSize( + viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgSize)), + utils2.GigaBytes, + ) + if err != nil { + return nil, err + } + volumeProper.SetSize(float32(size)) + } + volumeProper.SetName(viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgVolumeName))) volumeProper.SetBus(viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgBus))) diff --git a/commands/compute/server/server.go b/commands/compute/server/server.go index 005a2609a1..ca68b208ca 100644 --- a/commands/compute/server/server.go +++ b/commands/compute/server/server.go @@ -35,6 +35,7 @@ var AllServerCols = []table.Column{ {Name: "BootCdromId", JSONPath: "properties.bootCdrom.id"}, {Name: "BootVolumeId", JSONPath: "properties.bootVolume.id"}, {Name: "NicMultiQueue", JSONPath: "properties.nicMultiQueue"}, + {Name: "EnabledFeatures", JSONPath: "properties.enabledFeatures"}, } func ServerCmd() *core.Command { diff --git a/docs/subcommands/Compute Engine/image/delete.md b/docs/subcommands/Compute Engine/image/delete.md index 342073aa0d..7ac68410c6 100644 --- a/docs/subcommands/Compute Engine/image/delete.md +++ b/docs/subcommands/Compute Engine/image/delete.md @@ -38,7 +38,7 @@ Required values to run command: -a, --all Delete all non-public images -u, --api-url string Override default host URL. Preferred over the config file override 'cloud' and env var 'IONOS_API_URL' (default "https://api.ionos.com") --cols strings Set of columns to be printed on output - Available columns: [ImageId Name ImageAliases Location LicenceType ImageType CloudInit CreatedDate Size Description Public CreatedBy CreatedByUserId ExposeSerial RequireLegacyBios ApplicationType] + Available columns: [ImageId Name ImageAliases Location LicenceType ImageType CloudInit CreatedDate Size Description Public CreatedBy CreatedByUserId ExposeSerial RequireLegacyBios ApplicationType RequiredFeatures] -c, --config string Configuration file used for authentication (default "$XDG_CONFIG_HOME/ionosctl/config.yaml") -D, --depth int Level of detail for response objects (default 1) -F, --filters strings Limit results to results containing the specified filter:KEY1=VALUE1,KEY2=VALUE2 diff --git a/docs/subcommands/Compute Engine/image/get.md b/docs/subcommands/Compute Engine/image/get.md index 94cabb568e..5923d7ad99 100644 --- a/docs/subcommands/Compute Engine/image/get.md +++ b/docs/subcommands/Compute Engine/image/get.md @@ -37,7 +37,7 @@ Required values to run command: ```text -u, --api-url string Override default host URL. Preferred over the config file override 'cloud' and env var 'IONOS_API_URL' (default "https://api.ionos.com") --cols strings Set of columns to be printed on output - Available columns: [ImageId Name ImageAliases Location LicenceType ImageType CloudInit CreatedDate Size Description Public CreatedBy CreatedByUserId ExposeSerial RequireLegacyBios ApplicationType] + Available columns: [ImageId Name ImageAliases Location LicenceType ImageType CloudInit CreatedDate Size Description Public CreatedBy CreatedByUserId ExposeSerial RequireLegacyBios ApplicationType RequiredFeatures] -c, --config string Configuration file used for authentication (default "$XDG_CONFIG_HOME/ionosctl/config.yaml") -D, --depth int Level of detail for response objects (default 1) -F, --filters strings Limit results to results containing the specified filter:KEY1=VALUE1,KEY2=VALUE2 diff --git a/docs/subcommands/Compute Engine/image/list.md b/docs/subcommands/Compute Engine/image/list.md index 09c240aca4..cd6e0d634c 100644 --- a/docs/subcommands/Compute Engine/image/list.md +++ b/docs/subcommands/Compute Engine/image/list.md @@ -30,7 +30,7 @@ Use this command to get a full list of available public Images. You can filter the results using `--filters` option. Use the following format to set filters: `--filters KEY1=VALUE1,KEY2=VALUE2`. Available Filters: -* filter by property: [name description location size cpuHotPlug cpuHotUnplug ramHotPlug ramHotUnplug nicHotPlug nicHotUnplug discVirtioHotPlug discVirtioHotUnplug discScsiHotPlug discScsiHotUnplug exposeSerial requireLegacyBios licenceType applicationType imageType public imageAliases cloudInit] +* filter by property: [name description location size cpuHotPlug cpuHotUnplug ramHotPlug ramHotUnplug nicHotPlug nicHotUnplug discVirtioHotPlug discVirtioHotUnplug discScsiHotPlug discScsiHotUnplug exposeSerial requireLegacyBios licenceType applicationType imageType public imageAliases requiredFeatures cloudInit] * filter by metadata: [etag createdDate createdBy createdByUserId lastModifiedDate lastModifiedBy lastModifiedByUserId state] ## Options @@ -38,7 +38,7 @@ Available Filters: ```text -u, --api-url string Override default host URL. Preferred over the config file override 'cloud' and env var 'IONOS_API_URL' (default "https://api.ionos.com") --cols strings Set of columns to be printed on output - Available columns: [ImageId Name ImageAliases Location LicenceType ImageType CloudInit CreatedDate Size Description Public CreatedBy CreatedByUserId ExposeSerial RequireLegacyBios ApplicationType] + Available columns: [ImageId Name ImageAliases Location LicenceType ImageType CloudInit CreatedDate Size Description Public CreatedBy CreatedByUserId ExposeSerial RequireLegacyBios ApplicationType RequiredFeatures] -c, --config string Configuration file used for authentication (default "$XDG_CONFIG_HOME/ionosctl/config.yaml") -D, --depth int Level of detail for response objects (default 1) -F, --filters strings Limit results to results containing the specified filter:KEY1=VALUE1,KEY2=VALUE2 diff --git a/docs/subcommands/Compute Engine/image/update.md b/docs/subcommands/Compute Engine/image/update.md index c17a2a8345..4d10fcbb39 100644 --- a/docs/subcommands/Compute Engine/image/update.md +++ b/docs/subcommands/Compute Engine/image/update.md @@ -39,7 +39,7 @@ Required values to run command: --application-type string The type of application that is hosted on this resource. Can be one of: MSSQL-2019-Web, MSSQL-2019-Standard, MSSQL-2019-Enterprise, MSSQL-2022-Web, MSSQL-2022-Standard, MSSQL-2022-Enterprise, UNKNOWN (default "UNKNOWN") --cloud-init string Cloud init compatibility. Can be one of: V1, NONE (default "V1") --cols strings Set of columns to be printed on output - Available columns: [ImageId Name ImageAliases Location LicenceType ImageType CloudInit CreatedDate Size Description Public CreatedBy CreatedByUserId ExposeSerial RequireLegacyBios ApplicationType] + Available columns: [ImageId Name ImageAliases Location LicenceType ImageType CloudInit CreatedDate Size Description Public CreatedBy CreatedByUserId ExposeSerial RequireLegacyBios ApplicationType RequiredFeatures] -c, --config string Configuration file used for authentication (default "$XDG_CONFIG_HOME/ionosctl/config.yaml") --cpu-hot-plug 'Hot-Plug' CPU. It is not possible to have a hot-unplug CPU which you previously did not hot-plug (default true) --cpu-hot-unplug 'Hot-Unplug' CPU. It is not possible to have a hot-unplug CPU which you previously did not hot-plug diff --git a/docs/subcommands/Compute Engine/image/upload.md b/docs/subcommands/Compute Engine/image/upload.md index b3c92845e5..e301d5e63b 100644 --- a/docs/subcommands/Compute Engine/image/upload.md +++ b/docs/subcommands/Compute Engine/image/upload.md @@ -36,7 +36,7 @@ High level steps: 4. Print the resulting image objects to stdout in the chosen table or JSON format. AUTH AND SAFETY - - The FTP server relies on API credentials via environment variables IONOS_USERNAME and IONOS_PASSWORD. You can debug your current setup with "ionosctl whoami --provenance". + - The FTP server relies on basic API credentials via environment variables IONOS_USERNAME and IONOS_PASSWORD. A bearer token (IONOS_TOKEN) cannot be used for the FTP upload, so if you authenticate with a token you must additionally set IONOS_USERNAME and IONOS_PASSWORD (they may be set alongside IONOS_TOKEN). You can debug your current setup with "ionosctl whoami --provenance". - Use --skip-update to skip the API PATCH step if you only want to perform an FTP upload and not modify images through the API. - Use --skip-verify to skip verifying the FTP server certificate. Only use that for trusted servers. Skipping certificate verification can expose you to man-in-the-middle attacks. - If using a custom FTP server it is advised to use a self-signed certificate instead of --skip-verify. Provide its PEM file via --crt-path. The file should contain the server certificate in base64 PEM format. @@ -81,7 +81,8 @@ EXAMPLES --application-type string The type of application that is hosted on this resource. Can be one of: MSSQL-2019-Web, MSSQL-2019-Standard, MSSQL-2019-Enterprise, MSSQL-2022-Web, MSSQL-2022-Standard, MSSQL-2022-Enterprise, UNKNOWN (default "UNKNOWN") --cloud-init string Cloud init compatibility. Can be one of: V1, NONE (default "V1") --cols strings Set of columns to be printed on output - Available columns: [ImageId Name ImageAliases Location LicenceType ImageType CloudInit CreatedDate Size Description Public CreatedBy CreatedByUserId ExposeSerial RequireLegacyBios ApplicationType] + Available columns: [ImageId Name ImageAliases Location LicenceType ImageType CloudInit CreatedDate Size Description Public CreatedBy CreatedByUserId ExposeSerial RequireLegacyBios ApplicationType RequiredFeatures] + --confidential Upload to the confidential-images/ directory for Confidential Computing (CoCo) images. Requires a QCOW2 image with an embedded LAUNCH_ARTIFACTS partition. Forces cloud-init NONE and disables hot-plug / legacy BIOS on the image. -c, --config string Configuration file used for authentication (default "$XDG_CONFIG_HOME/ionosctl/config.yaml") --cpu-hot-plug 'Hot-Plug' CPU. It is not possible to have a hot-unplug CPU which you previously did not hot-plug (default true) --cpu-hot-unplug 'Hot-Unplug' CPU. It is not possible to have a hot-unplug CPU which you previously did not hot-plug diff --git a/docs/subcommands/Compute Engine/location/cpu/list.md b/docs/subcommands/Compute Engine/location/cpu/list.md index 8b78c57b11..95bbefa3a0 100644 --- a/docs/subcommands/Compute Engine/location/cpu/list.md +++ b/docs/subcommands/Compute Engine/location/cpu/list.md @@ -37,7 +37,7 @@ Required values to run command: ```text -u, --api-url string Override default host URL. Preferred over the config file override 'cloud' and env var 'IONOS_API_URL' (default "https://api.ionos.com") --cols strings Set of columns to be printed on output - Available columns: [CpuFamily MaxCores MaxRam Vendor] + Available columns: [CpuFamily MaxCores MaxRam Vendor EnabledFeatures] -c, --config string Configuration file used for authentication (default "$XDG_CONFIG_HOME/ionosctl/config.yaml") -D, --depth int Level of detail for response objects (default 1) -F, --filters strings Limit results to results containing the specified filter:KEY1=VALUE1,KEY2=VALUE2 diff --git a/docs/subcommands/Compute Engine/location/get.md b/docs/subcommands/Compute Engine/location/get.md index c3ee1ea807..e26f45aa0c 100644 --- a/docs/subcommands/Compute Engine/location/get.md +++ b/docs/subcommands/Compute Engine/location/get.md @@ -37,7 +37,7 @@ Required values to run command: ```text -u, --api-url string Override default host URL. Preferred over the config file override 'cloud' and env var 'IONOS_API_URL' (default "https://api.ionos.com") --cols strings Set of columns to be printed on output - Available columns: [LocationId Name Features ImageAliases CpuFamily] + Available columns: [LocationId Name Features ImageAliases CpuFamily CpuEnabledFeatures] -c, --config string Configuration file used for authentication (default "$XDG_CONFIG_HOME/ionosctl/config.yaml") -D, --depth int Level of detail for response objects (default 1) -F, --filters strings Limit results to results containing the specified filter:KEY1=VALUE1,KEY2=VALUE2 diff --git a/docs/subcommands/Compute Engine/location/list.md b/docs/subcommands/Compute Engine/location/list.md index c33390fd10..4a25c0bca4 100644 --- a/docs/subcommands/Compute Engine/location/list.md +++ b/docs/subcommands/Compute Engine/location/list.md @@ -30,7 +30,7 @@ Use this command to get a list of available locations to create objects on. You can filter the results using `--filters` option. Use the following format to set filters: `--filters KEY1=VALUE1,KEY2=VALUE2`. Available Filters: -* filter by property: [name features imageAliases] +* filter by property: [name metroRegion features imageAliases] * filter by metadata: [etag createdDate createdBy createdByUserId lastModifiedDate lastModifiedBy lastModifiedByUserId state] ## Options @@ -38,7 +38,7 @@ Available Filters: ```text -u, --api-url string Override default host URL. Preferred over the config file override 'cloud' and env var 'IONOS_API_URL' (default "https://api.ionos.com") --cols strings Set of columns to be printed on output - Available columns: [LocationId Name Features ImageAliases CpuFamily] + Available columns: [LocationId Name Features ImageAliases CpuFamily CpuEnabledFeatures] -c, --config string Configuration file used for authentication (default "$XDG_CONFIG_HOME/ionosctl/config.yaml") -D, --depth int Level of detail for response objects (default 1) -F, --filters strings Limit results to results containing the specified filter:KEY1=VALUE1,KEY2=VALUE2 diff --git a/docs/subcommands/Compute Engine/server/cdrom/list.md b/docs/subcommands/Compute Engine/server/cdrom/list.md index 0691bf0d09..34bf4287b0 100644 --- a/docs/subcommands/Compute Engine/server/cdrom/list.md +++ b/docs/subcommands/Compute Engine/server/cdrom/list.md @@ -36,7 +36,7 @@ Use this command to retrieve a list of CD-ROMs attached to the Server. You can filter the results using `--filters` option. Use the following format to set filters: `--filters KEY1=VALUE1,KEY2=VALUE2`. Available Filters: -* filter by property: [name description location size cpuHotPlug cpuHotUnplug ramHotPlug ramHotUnplug nicHotPlug nicHotUnplug discVirtioHotPlug discVirtioHotUnplug discScsiHotPlug discScsiHotUnplug exposeSerial requireLegacyBios licenceType applicationType imageType public imageAliases cloudInit] +* filter by property: [name description location size cpuHotPlug cpuHotUnplug ramHotPlug ramHotUnplug nicHotPlug nicHotUnplug discVirtioHotPlug discVirtioHotUnplug discScsiHotPlug discScsiHotUnplug exposeSerial requireLegacyBios licenceType applicationType imageType public imageAliases requiredFeatures cloudInit] * filter by metadata: [etag createdDate createdBy createdByUserId lastModifiedDate lastModifiedBy lastModifiedByUserId state] Required values to run command: diff --git a/docs/subcommands/Compute Engine/server/console/get.md b/docs/subcommands/Compute Engine/server/console/get.md index a031f108b2..d9fdf21ac1 100644 --- a/docs/subcommands/Compute Engine/server/console/get.md +++ b/docs/subcommands/Compute Engine/server/console/get.md @@ -44,7 +44,7 @@ Required values to run command: ```text -u, --api-url string Override default host URL. Preferred over the config file override 'cloud' and env var 'IONOS_API_URL' (default "https://api.ionos.com") --cols strings Set of columns to be printed on output - Available columns: [ServerId Name Type AvailabilityZone Cores RAM CpuFamily VmState State DatacenterId TemplateId BootCdromId BootVolumeId NicMultiQueue] + Available columns: [ServerId Name Type AvailabilityZone Cores RAM CpuFamily VmState State DatacenterId TemplateId BootCdromId BootVolumeId NicMultiQueue EnabledFeatures] -c, --config string Configuration file used for authentication (default "$XDG_CONFIG_HOME/ionosctl/config.yaml") --datacenter-id string The unique Data Center Id (required) -D, --depth int Level of detail for response objects (default 1) diff --git a/docs/subcommands/Compute Engine/server/create.md b/docs/subcommands/Compute Engine/server/create.md index 76361fc5bc..ab15be8b71 100644 --- a/docs/subcommands/Compute Engine/server/create.md +++ b/docs/subcommands/Compute Engine/server/create.md @@ -92,7 +92,8 @@ Use `--wait` (`-w`) to wait for the resource to reach AVAILABLE state. -z, --availability-zone string Availability zone of the Server (default "AUTO") --bus string [CUBE Server] The bus type of the Direct Attached Storage (default "VIRTIO") --cols strings Set of columns to be printed on output - Available columns: [ServerId Name Type AvailabilityZone Cores RAM CpuFamily VmState State DatacenterId TemplateId BootCdromId BootVolumeId NicMultiQueue] + Available columns: [ServerId Name Type AvailabilityZone Cores RAM CpuFamily VmState State DatacenterId TemplateId BootCdromId BootVolumeId NicMultiQueue EnabledFeatures] + --confidential Create a Confidential Computing (SEV-SNP) VM from a confidential boot image. Requires --type ENTERPRISE and --image-id (a private, SEV-SNP image). Do not set --cores or --cpu-family: both are derived from the image's launch-config.json. A boot volume is created from --image-id and attached automatically; size it with --size and --storage-type. -c, --config string Configuration file used for authentication (default "$XDG_CONFIG_HOME/ionosctl/config.yaml") --cores int The total number of cores for the Server, e.g. 4. Maximum: depends on contract resource limits (required) (default 2) --cpu-family string CPU Family for the Server. For CUBE Servers, the CPU Family is INTEL_SKYLAKE. If the flag is not set, the CPU Family will be chosen based on the location of the Datacenter. It will always be the first CPU Family available, as returned by the API (default "AUTO") @@ -116,7 +117,9 @@ Use `--wait` (`-w`) to wait for the resource to reach AVAILABLE state. --query string JMESPath query string to filter the output -q, --quiet Quiet output --ram string The amount of memory for the Server. Size must be specified in multiples of 256. e.g. --ram 256 or --ram 256MB (required) + --size string [Confidential] Size of the confidential boot volume, e.g. --size 10 or --size 10GB (default "10") -k, --ssh-key-paths strings [CUBE Server] Absolute paths for the SSH Keys of the Direct Attached Storage + --storage-type string [Confidential] Storage type of the confidential boot volume. Can be one of: HDD, SSD, SSD Standard, SSD Premium (default "HDD") --template-id string [CUBE Server] The unique Template Id (required) -t, --timeout int Timeout in seconds for --wait and other wait operations (default 600) --type string Type usages for the Server. Can be one of: ENTERPRISE, CUBE, VCPU, GPU (default "ENTERPRISE") diff --git a/docs/subcommands/Compute Engine/server/delete.md b/docs/subcommands/Compute Engine/server/delete.md index 3cf0e77f0d..48d61034ee 100644 --- a/docs/subcommands/Compute Engine/server/delete.md +++ b/docs/subcommands/Compute Engine/server/delete.md @@ -43,7 +43,7 @@ Required values to run command: -a, --all Delete all Servers form a virtual Datacenter. -u, --api-url string Override default host URL. Preferred over the config file override 'cloud' and env var 'IONOS_API_URL' (default "https://api.ionos.com") --cols strings Set of columns to be printed on output - Available columns: [ServerId Name Type AvailabilityZone Cores RAM CpuFamily VmState State DatacenterId TemplateId BootCdromId BootVolumeId NicMultiQueue] + Available columns: [ServerId Name Type AvailabilityZone Cores RAM CpuFamily VmState State DatacenterId TemplateId BootCdromId BootVolumeId NicMultiQueue EnabledFeatures] -c, --config string Configuration file used for authentication (default "$XDG_CONFIG_HOME/ionosctl/config.yaml") --datacenter-id string The unique Data Center Id (required) -D, --depth int Level of detail for response objects (default 1) diff --git a/docs/subcommands/Compute Engine/server/get.md b/docs/subcommands/Compute Engine/server/get.md index 5698e0acb9..cbb124f46a 100644 --- a/docs/subcommands/Compute Engine/server/get.md +++ b/docs/subcommands/Compute Engine/server/get.md @@ -40,7 +40,7 @@ Required values to run command: ```text -u, --api-url string Override default host URL. Preferred over the config file override 'cloud' and env var 'IONOS_API_URL' (default "https://api.ionos.com") --cols strings Set of columns to be printed on output - Available columns: [ServerId Name Type AvailabilityZone Cores RAM CpuFamily VmState State DatacenterId TemplateId BootCdromId BootVolumeId NicMultiQueue] + Available columns: [ServerId Name Type AvailabilityZone Cores RAM CpuFamily VmState State DatacenterId TemplateId BootCdromId BootVolumeId NicMultiQueue EnabledFeatures] -c, --config string Configuration file used for authentication (default "$XDG_CONFIG_HOME/ionosctl/config.yaml") --datacenter-id string The unique Data Center Id (required) -D, --depth int Level of detail for response objects (default 1) diff --git a/docs/subcommands/Compute Engine/server/gpu/list.md b/docs/subcommands/Compute Engine/server/gpu/list.md index 989fbb5f5f..70fab952e5 100644 --- a/docs/subcommands/Compute Engine/server/gpu/list.md +++ b/docs/subcommands/Compute Engine/server/gpu/list.md @@ -46,7 +46,7 @@ Required values to run command: ```text -u, --api-url string Override default host URL. Preferred over the config file override 'cloud' and env var 'IONOS_API_URL' (default "https://api.ionos.com") --cols strings Set of columns to be printed on output - Available columns: [ServerId Name Type AvailabilityZone Cores RAM CpuFamily VmState State DatacenterId TemplateId BootCdromId BootVolumeId NicMultiQueue] + Available columns: [ServerId Name Type AvailabilityZone Cores RAM CpuFamily VmState State DatacenterId TemplateId BootCdromId BootVolumeId NicMultiQueue EnabledFeatures] -c, --config string Configuration file used for authentication (default "$XDG_CONFIG_HOME/ionosctl/config.yaml") --datacenter-id string The unique Data Center Id (required) -D, --depth int Level of detail for response objects (default 1) diff --git a/docs/subcommands/Compute Engine/server/list.md b/docs/subcommands/Compute Engine/server/list.md index b3fdc0a0df..f82bc86b52 100644 --- a/docs/subcommands/Compute Engine/server/list.md +++ b/docs/subcommands/Compute Engine/server/list.md @@ -30,7 +30,7 @@ Use this command to list Servers from a specified Virtual Data Center. You can filter the results using `--filters` option. Use the following format to set filters: `--filters KEY1=VALUE1,KEY2=VALUE2`. Available Filters: -* filter by property: [templateUuid name hostname cores ram availabilityZone vmState bootCdrom bootVolume cpuFamily type placementGroupId nicMultiQueue] +* filter by property: [templateUuid name hostname cores ram availabilityZone vmState bootCdrom bootVolume cpuFamily type enabledFeatures placementGroupId nicMultiQueue] * filter by metadata: [etag createdDate createdBy createdByUserId lastModifiedDate lastModifiedBy lastModifiedByUserId state] Required values to run command: @@ -43,7 +43,7 @@ Required values to run command: -a, --all List all resources without the need of specifying parent ID name. -u, --api-url string Override default host URL. Preferred over the config file override 'cloud' and env var 'IONOS_API_URL' (default "https://api.ionos.com") --cols strings Set of columns to be printed on output - Available columns: [ServerId Name Type AvailabilityZone Cores RAM CpuFamily VmState State DatacenterId TemplateId BootCdromId BootVolumeId NicMultiQueue] + Available columns: [ServerId Name Type AvailabilityZone Cores RAM CpuFamily VmState State DatacenterId TemplateId BootCdromId BootVolumeId NicMultiQueue EnabledFeatures] -c, --config string Configuration file used for authentication (default "$XDG_CONFIG_HOME/ionosctl/config.yaml") --datacenter-id string The unique Data Center Id (required) -D, --depth int Level of detail for response objects (default 1) diff --git a/docs/subcommands/Compute Engine/server/reboot.md b/docs/subcommands/Compute Engine/server/reboot.md index 51c61cf89b..cea594b8cb 100644 --- a/docs/subcommands/Compute Engine/server/reboot.md +++ b/docs/subcommands/Compute Engine/server/reboot.md @@ -40,7 +40,7 @@ Required values to run command: ```text -u, --api-url string Override default host URL. Preferred over the config file override 'cloud' and env var 'IONOS_API_URL' (default "https://api.ionos.com") --cols strings Set of columns to be printed on output - Available columns: [ServerId Name Type AvailabilityZone Cores RAM CpuFamily VmState State DatacenterId TemplateId BootCdromId BootVolumeId NicMultiQueue] + Available columns: [ServerId Name Type AvailabilityZone Cores RAM CpuFamily VmState State DatacenterId TemplateId BootCdromId BootVolumeId NicMultiQueue EnabledFeatures] -c, --config string Configuration file used for authentication (default "$XDG_CONFIG_HOME/ionosctl/config.yaml") --datacenter-id string The unique Data Center Id (required) -D, --depth int Level of detail for response objects (default 1) diff --git a/docs/subcommands/Compute Engine/server/resume.md b/docs/subcommands/Compute Engine/server/resume.md index b04c06f550..a02c8b8497 100644 --- a/docs/subcommands/Compute Engine/server/resume.md +++ b/docs/subcommands/Compute Engine/server/resume.md @@ -40,7 +40,7 @@ Required values to run command: ```text -u, --api-url string Override default host URL. Preferred over the config file override 'cloud' and env var 'IONOS_API_URL' (default "https://api.ionos.com") --cols strings Set of columns to be printed on output - Available columns: [ServerId Name Type AvailabilityZone Cores RAM CpuFamily VmState State DatacenterId TemplateId BootCdromId BootVolumeId NicMultiQueue] + Available columns: [ServerId Name Type AvailabilityZone Cores RAM CpuFamily VmState State DatacenterId TemplateId BootCdromId BootVolumeId NicMultiQueue EnabledFeatures] -c, --config string Configuration file used for authentication (default "$XDG_CONFIG_HOME/ionosctl/config.yaml") --datacenter-id string The unique Data Center Id (required) -D, --depth int Level of detail for response objects (default 1) diff --git a/docs/subcommands/Compute Engine/server/start.md b/docs/subcommands/Compute Engine/server/start.md index 3a38946b3f..7088346802 100644 --- a/docs/subcommands/Compute Engine/server/start.md +++ b/docs/subcommands/Compute Engine/server/start.md @@ -40,7 +40,7 @@ Required values to run command: ```text -u, --api-url string Override default host URL. Preferred over the config file override 'cloud' and env var 'IONOS_API_URL' (default "https://api.ionos.com") --cols strings Set of columns to be printed on output - Available columns: [ServerId Name Type AvailabilityZone Cores RAM CpuFamily VmState State DatacenterId TemplateId BootCdromId BootVolumeId NicMultiQueue] + Available columns: [ServerId Name Type AvailabilityZone Cores RAM CpuFamily VmState State DatacenterId TemplateId BootCdromId BootVolumeId NicMultiQueue EnabledFeatures] -c, --config string Configuration file used for authentication (default "$XDG_CONFIG_HOME/ionosctl/config.yaml") --datacenter-id string The unique Data Center Id (required) -D, --depth int Level of detail for response objects (default 1) diff --git a/docs/subcommands/Compute Engine/server/stop.md b/docs/subcommands/Compute Engine/server/stop.md index f01aa72c8c..e29d4622ed 100644 --- a/docs/subcommands/Compute Engine/server/stop.md +++ b/docs/subcommands/Compute Engine/server/stop.md @@ -40,7 +40,7 @@ Required values to run command: ```text -u, --api-url string Override default host URL. Preferred over the config file override 'cloud' and env var 'IONOS_API_URL' (default "https://api.ionos.com") --cols strings Set of columns to be printed on output - Available columns: [ServerId Name Type AvailabilityZone Cores RAM CpuFamily VmState State DatacenterId TemplateId BootCdromId BootVolumeId NicMultiQueue] + Available columns: [ServerId Name Type AvailabilityZone Cores RAM CpuFamily VmState State DatacenterId TemplateId BootCdromId BootVolumeId NicMultiQueue EnabledFeatures] -c, --config string Configuration file used for authentication (default "$XDG_CONFIG_HOME/ionosctl/config.yaml") --datacenter-id string The unique Data Center Id (required) -D, --depth int Level of detail for response objects (default 1) diff --git a/docs/subcommands/Compute Engine/server/suspend.md b/docs/subcommands/Compute Engine/server/suspend.md index 453944af8d..b9d214f2e6 100644 --- a/docs/subcommands/Compute Engine/server/suspend.md +++ b/docs/subcommands/Compute Engine/server/suspend.md @@ -34,7 +34,7 @@ Required values to run command: ```text -u, --api-url string Override default host URL. Preferred over the config file override 'cloud' and env var 'IONOS_API_URL' (default "https://api.ionos.com") --cols strings Set of columns to be printed on output - Available columns: [ServerId Name Type AvailabilityZone Cores RAM CpuFamily VmState State DatacenterId TemplateId BootCdromId BootVolumeId NicMultiQueue] + Available columns: [ServerId Name Type AvailabilityZone Cores RAM CpuFamily VmState State DatacenterId TemplateId BootCdromId BootVolumeId NicMultiQueue EnabledFeatures] -c, --config string Configuration file used for authentication (default "$XDG_CONFIG_HOME/ionosctl/config.yaml") --datacenter-id string The unique Data Center Id (required) -D, --depth int Level of detail for response objects (default 1) diff --git a/docs/subcommands/Compute Engine/server/token/get.md b/docs/subcommands/Compute Engine/server/token/get.md index e1bc6d9b8d..ca6e2fd05a 100644 --- a/docs/subcommands/Compute Engine/server/token/get.md +++ b/docs/subcommands/Compute Engine/server/token/get.md @@ -44,7 +44,7 @@ Required values to run command: ```text -u, --api-url string Override default host URL. Preferred over the config file override 'cloud' and env var 'IONOS_API_URL' (default "https://api.ionos.com") --cols strings Set of columns to be printed on output - Available columns: [ServerId Name Type AvailabilityZone Cores RAM CpuFamily VmState State DatacenterId TemplateId BootCdromId BootVolumeId NicMultiQueue] + Available columns: [ServerId Name Type AvailabilityZone Cores RAM CpuFamily VmState State DatacenterId TemplateId BootCdromId BootVolumeId NicMultiQueue EnabledFeatures] -c, --config string Configuration file used for authentication (default "$XDG_CONFIG_HOME/ionosctl/config.yaml") --datacenter-id string The unique Data Center Id (required) -D, --depth int Level of detail for response objects (default 1) diff --git a/docs/subcommands/Compute Engine/server/update.md b/docs/subcommands/Compute Engine/server/update.md index 69b3a0650e..0d78e8f553 100644 --- a/docs/subcommands/Compute Engine/server/update.md +++ b/docs/subcommands/Compute Engine/server/update.md @@ -51,7 +51,7 @@ Required values to run command: -z, --availability-zone string Availability zone of the Server --cdrom-id string The unique Cdrom Id for the BootCdrom. The Cdrom needs to be already attached to the Server --cols strings Set of columns to be printed on output - Available columns: [ServerId Name Type AvailabilityZone Cores RAM CpuFamily VmState State DatacenterId TemplateId BootCdromId BootVolumeId NicMultiQueue] + Available columns: [ServerId Name Type AvailabilityZone Cores RAM CpuFamily VmState State DatacenterId TemplateId BootCdromId BootVolumeId NicMultiQueue EnabledFeatures] -c, --config string Configuration file used for authentication (default "$XDG_CONFIG_HOME/ionosctl/config.yaml") --cores int The total number of cores for the Server, e.g. 4. Maximum: depends on contract resource limits (default 2) --cpu-family string CPU Family of the Server diff --git a/docs/subcommands/Compute Engine/template/list.md b/docs/subcommands/Compute Engine/template/list.md index c613b6bf8f..9ddd3d0b59 100644 --- a/docs/subcommands/Compute Engine/template/list.md +++ b/docs/subcommands/Compute Engine/template/list.md @@ -30,7 +30,7 @@ Use this command to get a list of available public Templates. You can filter the results using `--filters` option. Use the following format to set filters: `--filters KEY1=VALUE1,KEY2=VALUE2`. Available Filters: -* filter by property: [name cores ram storageSize category] +* filter by property: [name cores ram storageSize storageType category] * filter by metadata: [etag createdDate createdBy createdByUserId lastModifiedDate lastModifiedBy lastModifiedByUserId state] ## Options diff --git a/go.mod b/go.mod index 3c1d6d9101..0613adfb9c 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/ionos-cloud/sdk-go-bundle/shared v0.1.9 github.com/ionos-cloud/sdk-go-cert-manager v1.3.0 github.com/ionos-cloud/sdk-go-vm-autoscaling v1.1.1 - github.com/ionos-cloud/sdk-go/v6 v6.3.6 + github.com/ionos-cloud/sdk-go/v6 v6.3.11 github.com/ionoscloudsdk/comptplus v1.1.4 github.com/jmespath/go-jmespath v0.4.0 github.com/kardianos/ftps v1.0.4 diff --git a/go.sum b/go.sum index fc0285b649..567e789c21 100644 --- a/go.sum +++ b/go.sum @@ -190,8 +190,8 @@ github.com/ionos-cloud/sdk-go-cert-manager v1.3.0 h1:VMbD/XgLmMV2d7uI1+xf/uzRZWK github.com/ionos-cloud/sdk-go-cert-manager v1.3.0/go.mod h1:8CPWJBuryfrUpiPNrVIPry6qQjZWfIhmRpFkqKiaO2w= github.com/ionos-cloud/sdk-go-vm-autoscaling v1.1.1 h1:8sQLfOxAqquRahECC0VoNvBo0tbhbFYXKudqlmSaDA8= github.com/ionos-cloud/sdk-go-vm-autoscaling v1.1.1/go.mod h1:On4pvBXk/ZvM/xldqHGukHokLtabIrNzYDlxOs5IVyo= -github.com/ionos-cloud/sdk-go/v6 v6.3.6 h1:l/TtKgdQ1wUH3DDe2SfFD78AW+TJWdEbDpQhHkWd6CM= -github.com/ionos-cloud/sdk-go/v6 v6.3.6/go.mod h1:nUGHP4kZHAZngCVr4v6C8nuargFrtvt7GrzH/hqn7c4= +github.com/ionos-cloud/sdk-go/v6 v6.3.11 h1:m4mX03vflm631znIPutojJxLZvxD5Mt3xv/soy0kLIc= +github.com/ionos-cloud/sdk-go/v6 v6.3.11/go.mod h1:nUGHP4kZHAZngCVr4v6C8nuargFrtvt7GrzH/hqn7c4= github.com/ionoscloudsdk/comptplus v1.1.4 h1:KI5Z2raPn5JJ21K0kxFGWkvlpvQKLCbiEMUzecScvtA= github.com/ionoscloudsdk/comptplus v1.1.4/go.mod h1:WwR/SUMI6bVdXRakPUjtpuTNtG+u4Crj3Km0uuSriVw= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= diff --git a/internal/constants/constants.go b/internal/constants/constants.go index 6a85cd5b16..340d1c3941 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -161,6 +161,7 @@ Within each layer, a token takes precedence over a username and password combina FlagAvailabilityZone = "availability-zone" FlagAvailabilityZoneShort = "z" FlagCpuFamily = "cpu-family" + FlagConfidential = "confidential" FlagStorageType = "storage-type" FlagStorageSize = "storage-size" FlagServerType = "server-type" diff --git a/vendor/github.com/ionos-cloud/sdk-go/v6/README.md b/vendor/github.com/ionos-cloud/sdk-go/v6/README.md index 438f910842..da0cf3c0ca 100644 --- a/vendor/github.com/ionos-cloud/sdk-go/v6/README.md +++ b/vendor/github.com/ionos-cloud/sdk-go/v6/README.md @@ -657,6 +657,7 @@ All URIs are relative to *https://api.ionos.com/cloudapi/v6* - [KubernetesNodePoolPropertiesForPost](docs/models/KubernetesNodePoolPropertiesForPost) - [KubernetesNodePoolPropertiesForPut](docs/models/KubernetesNodePoolPropertiesForPut) - [KubernetesNodePoolServerType](docs/models/KubernetesNodePoolServerType) + - [KubernetesNodePoolTaint](docs/models/KubernetesNodePoolTaint) - [KubernetesNodePools](docs/models/KubernetesNodePools) - [KubernetesNodeProperties](docs/models/KubernetesNodeProperties) - [KubernetesNodes](docs/models/KubernetesNodes) @@ -751,6 +752,7 @@ All URIs are relative to *https://api.ionos.com/cloudapi/v6* - [Snapshot](docs/models/Snapshot) - [SnapshotProperties](docs/models/SnapshotProperties) - [Snapshots](docs/models/Snapshots) + - [TaintEffect](docs/models/TaintEffect) - [TargetGroup](docs/models/TargetGroup) - [TargetGroupHealthCheck](docs/models/TargetGroupHealthCheck) - [TargetGroupHttpHealthCheck](docs/models/TargetGroupHttpHealthCheck) diff --git a/vendor/github.com/ionos-cloud/sdk-go/v6/api_servers.go b/vendor/github.com/ionos-cloud/sdk-go/v6/api_servers.go index 8c1faf2e70..b216dc6b13 100644 --- a/vendor/github.com/ionos-cloud/sdk-go/v6/api_servers.go +++ b/vendor/github.com/ionos-cloud/sdk-go/v6/api_servers.go @@ -2310,6 +2310,15 @@ func (a *ServersApiService) DatacentersServersRemoteConsoleGetExecute(r ApiDatac body: localVarBody, error: fmt.Sprintf(FormatStringErr, localVarHTTPResponse.Status, string(localVarBody)), } + if localVarHTTPResponse.StatusCode == 422 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = fmt.Sprintf(FormatStringErr, localVarHTTPResponse.Status, err.Error()) + return localVarReturnValue, localVarAPIResponse, newErr + } + newErr.model = v + } var v Error err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) if err != nil { diff --git a/vendor/github.com/ionos-cloud/sdk-go/v6/client.go b/vendor/github.com/ionos-cloud/sdk-go/v6/client.go index 69bb8d11af..8d777798b9 100644 --- a/vendor/github.com/ionos-cloud/sdk-go/v6/client.go +++ b/vendor/github.com/ionos-cloud/sdk-go/v6/client.go @@ -52,7 +52,7 @@ const ( RequestStatusFailed = "FAILED" RequestStatusDone = "DONE" - Version = "6.3.6" + Version = "6.3.11" ) // Constants for APIs @@ -364,6 +364,9 @@ func (c *APIClient) callAPI(request *http.Request) (*http.Response, time.Duratio resp, err = c.cfg.HTTPClient.Do(clonedRequest) httpRequestTime = time.Since(httpRequestStartTime) if err != nil { + if c.cfg.Debug || c.cfg.LogLevel.Satisfies(Debug) { + c.cfg.Logger.Printf("[DEBUG] cloudapi: request failed for %s %s: %v\n", clonedRequest.Method, clonedRequest.URL.Host, err) + } return resp, httpRequestTime, err } @@ -379,12 +382,28 @@ func (c *APIClient) callAPI(request *http.Request) (*http.Response, time.Duratio var backoffTime time.Duration switch resp.StatusCode { + case http.StatusInternalServerError: + // Only retry 500s on GET to avoid retrying potentially + // non-idempotent requests. + if request.Method != http.MethodGet { + return resp, httpRequestTime, err + } + if c.cfg.Debug || c.cfg.LogLevel.Satisfies(Debug) { + c.cfg.Logger.Printf("[DEBUG] ionoscloud: received %d for %s %s, will retry (attempt %d/%d)\n", resp.StatusCode, request.Method, request.URL.String(), retryCount, c.GetConfig().MaxRetries) + } + backoffTime = c.GetConfig().WaitTime case http.StatusServiceUnavailable, http.StatusGatewayTimeout, http.StatusBadGateway: if request.Method == http.MethodPost { + if c.cfg.Debug || c.cfg.LogLevel.Satisfies(Debug) { + c.cfg.Logger.Printf("[DEBUG] ionoscloud: received %d for POST %s, not retrying (non-idempotent)\n", resp.StatusCode, request.URL.String()) + } return resp, httpRequestTime, err } + if c.cfg.Debug || c.cfg.LogLevel.Satisfies(Debug) { + c.cfg.Logger.Printf("[DEBUG] ionoscloud: received %d for %s %s, will retry (attempt %d/%d)\n", resp.StatusCode, request.Method, request.URL.String(), retryCount, c.GetConfig().MaxRetries) + } backoffTime = c.GetConfig().WaitTime case http.StatusTooManyRequests: @@ -394,8 +413,14 @@ func (c *APIClient) callAPI(request *http.Request) (*http.Response, time.Duratio return resp, httpRequestTime, err } backoffTime = waitTime + if c.cfg.Debug || c.cfg.LogLevel.Satisfies(Debug) { + c.cfg.Logger.Printf("[DEBUG] ionoscloud: rate limited (429) for %s %s, retry-after=%ss (attempt %d/%d)\n", request.Method, request.URL.String(), retryAfterSeconds, retryCount, c.GetConfig().MaxRetries) + } } else { backoffTime = c.GetConfig().WaitTime + if c.cfg.Debug || c.cfg.LogLevel.Satisfies(Debug) { + c.cfg.Logger.Printf("[DEBUG] ionoscloud: rate limited (429) for %s %s, using default backoff (attempt %d/%d)\n", request.Method, request.URL.String(), retryCount, c.GetConfig().MaxRetries) + } } default: return resp, httpRequestTime, err @@ -404,10 +429,12 @@ func (c *APIClient) callAPI(request *http.Request) (*http.Response, time.Duratio if retryCount >= c.GetConfig().MaxRetries { if c.cfg.Debug || c.cfg.LogLevel.Satisfies(Debug) { - c.cfg.Logger.Printf(" Number of maximum retries exceeded (%d retries)\n", c.cfg.MaxRetries) + c.cfg.Logger.Printf("[DEBUG] ionoscloud: retry exhausted after %d attempts for %s %s, last status=%d\n", retryCount, request.Method, request.URL.String(), resp.StatusCode) } break } else { + _, _ = io.Copy(io.Discard, resp.Body) + _ = resp.Body.Close() c.backOff(request.Context(), backoffTime) } } diff --git a/vendor/github.com/ionos-cloud/sdk-go/v6/configuration.go b/vendor/github.com/ionos-cloud/sdk-go/v6/configuration.go index 363d43bb03..4b30350b5c 100644 --- a/vendor/github.com/ionos-cloud/sdk-go/v6/configuration.go +++ b/vendor/github.com/ionos-cloud/sdk-go/v6/configuration.go @@ -133,7 +133,7 @@ func NewConfiguration(username, password, token, hostUrl string) *Configuration cfg := &Configuration{ DefaultHeader: make(map[string]string), DefaultQueryParams: url.Values{}, - UserAgent: "ionos-cloud-sdk-go/v6.3.6", + UserAgent: "ionos-cloud-sdk-go/v6.3.11", Debug: false, Username: username, Password: password, diff --git a/vendor/github.com/ionos-cloud/sdk-go/v6/model_cpu_architecture_properties.go b/vendor/github.com/ionos-cloud/sdk-go/v6/model_cpu_architecture_properties.go index 2950bbf356..a13156bab4 100644 --- a/vendor/github.com/ionos-cloud/sdk-go/v6/model_cpu_architecture_properties.go +++ b/vendor/github.com/ionos-cloud/sdk-go/v6/model_cpu_architecture_properties.go @@ -24,6 +24,8 @@ type CpuArchitectureProperties struct { MaxRam *int32 `json:"maxRam,omitempty"` // A valid CPU vendor name. Vendor *string `json:"vendor,omitempty"` + // A list of enabled CPU features. + EnabledFeatures *[]string `json:"enabledFeatures,omitempty"` } // NewCpuArchitectureProperties instantiates a new CpuArchitectureProperties object @@ -196,6 +198,44 @@ func (o *CpuArchitectureProperties) HasVendor() bool { return false } +// GetEnabledFeatures returns the EnabledFeatures field value +// If the value is explicit nil, nil is returned +func (o *CpuArchitectureProperties) GetEnabledFeatures() *[]string { + if o == nil { + return nil + } + + return o.EnabledFeatures + +} + +// GetEnabledFeaturesOk returns a tuple with the EnabledFeatures field value +// and a boolean to check if the value has been set. +// NOTE: If the value is an explicit nil, `nil, true` will be returned +func (o *CpuArchitectureProperties) GetEnabledFeaturesOk() (*[]string, bool) { + if o == nil { + return nil, false + } + + return o.EnabledFeatures, true +} + +// SetEnabledFeatures sets field value +func (o *CpuArchitectureProperties) SetEnabledFeatures(v []string) { + + o.EnabledFeatures = &v + +} + +// HasEnabledFeatures returns a boolean if a field has been set. +func (o *CpuArchitectureProperties) HasEnabledFeatures() bool { + if o != nil && o.EnabledFeatures != nil { + return true + } + + return false +} + func (o CpuArchitectureProperties) MarshalJSON() ([]byte, error) { toSerialize := map[string]interface{}{} if o.CpuFamily != nil { @@ -214,6 +254,10 @@ func (o CpuArchitectureProperties) MarshalJSON() ([]byte, error) { toSerialize["vendor"] = o.Vendor } + if o.EnabledFeatures != nil { + toSerialize["enabledFeatures"] = o.EnabledFeatures + } + return json.Marshal(toSerialize) } diff --git a/vendor/github.com/ionos-cloud/sdk-go/v6/model_image_properties.go b/vendor/github.com/ionos-cloud/sdk-go/v6/model_image_properties.go index 830245b1d3..d0481cdeea 100644 --- a/vendor/github.com/ionos-cloud/sdk-go/v6/model_image_properties.go +++ b/vendor/github.com/ionos-cloud/sdk-go/v6/model_image_properties.go @@ -58,6 +58,8 @@ type ImageProperties struct { Public *bool `json:"public,omitempty"` // List of image aliases mapped for this image ImageAliases *[]string `json:"imageAliases,omitempty"` + // The list of features required by this image. If `SEV-SNP` is part of this list, then the image supports Confidential Computing. + RequiredFeatures *[]string `json:"requiredFeatures,omitempty"` // Cloud init compatibility. CloudInit *string `json:"cloudInit,omitempty"` } @@ -888,6 +890,44 @@ func (o *ImageProperties) HasImageAliases() bool { return false } +// GetRequiredFeatures returns the RequiredFeatures field value +// If the value is explicit nil, nil is returned +func (o *ImageProperties) GetRequiredFeatures() *[]string { + if o == nil { + return nil + } + + return o.RequiredFeatures + +} + +// GetRequiredFeaturesOk returns a tuple with the RequiredFeatures field value +// and a boolean to check if the value has been set. +// NOTE: If the value is an explicit nil, `nil, true` will be returned +func (o *ImageProperties) GetRequiredFeaturesOk() (*[]string, bool) { + if o == nil { + return nil, false + } + + return o.RequiredFeatures, true +} + +// SetRequiredFeatures sets field value +func (o *ImageProperties) SetRequiredFeatures(v []string) { + + o.RequiredFeatures = &v + +} + +// HasRequiredFeatures returns a boolean if a field has been set. +func (o *ImageProperties) HasRequiredFeatures() bool { + if o != nil && o.RequiredFeatures != nil { + return true + } + + return false +} + // GetCloudInit returns the CloudInit field value // If the value is explicit nil, nil is returned func (o *ImageProperties) GetCloudInit() *string { @@ -1012,6 +1052,10 @@ func (o ImageProperties) MarshalJSON() ([]byte, error) { toSerialize["imageAliases"] = o.ImageAliases } + if o.RequiredFeatures != nil { + toSerialize["requiredFeatures"] = o.RequiredFeatures + } + if o.CloudInit != nil { toSerialize["cloudInit"] = o.CloudInit } diff --git a/vendor/github.com/ionos-cloud/sdk-go/v6/model_kubernetes_node_pool_properties.go b/vendor/github.com/ionos-cloud/sdk-go/v6/model_kubernetes_node_pool_properties.go index d51b558ee6..fe15f792d8 100644 --- a/vendor/github.com/ionos-cloud/sdk-go/v6/model_kubernetes_node_pool_properties.go +++ b/vendor/github.com/ionos-cloud/sdk-go/v6/model_kubernetes_node_pool_properties.go @@ -45,6 +45,8 @@ type KubernetesNodePoolProperties struct { Labels *map[string]string `json:"labels,omitempty"` // The annotations attached to the node pool. Annotations *map[string]string `json:"annotations,omitempty"` + // Taints applied to nodes in this pool. A taint repels pods that do not have a matching toleration. Maximum 50 taints per node pool. + Taints *[]KubernetesNodePoolTaint `json:"taints,omitempty"` // Optional array of reserved public IP addresses to be used by the nodes. The IPs must be from the exact location of the node pool's data center. If autoscaling is used, the array must contain one more IP than the maximum possible number of nodes (nodeCount+1 for a fixed number of nodes or maxNodeCount+1). The extra IP is used when the nodes are rebuilt. PublicIps *[]string `json:"publicIps,omitempty"` // The list of available versions for upgrading the node pool. @@ -690,6 +692,44 @@ func (o *KubernetesNodePoolProperties) HasAnnotations() bool { return false } +// GetTaints returns the Taints field value +// If the value is explicit nil, nil is returned +func (o *KubernetesNodePoolProperties) GetTaints() *[]KubernetesNodePoolTaint { + if o == nil { + return nil + } + + return o.Taints + +} + +// GetTaintsOk returns a tuple with the Taints field value +// and a boolean to check if the value has been set. +// NOTE: If the value is an explicit nil, `nil, true` will be returned +func (o *KubernetesNodePoolProperties) GetTaintsOk() (*[]KubernetesNodePoolTaint, bool) { + if o == nil { + return nil, false + } + + return o.Taints, true +} + +// SetTaints sets field value +func (o *KubernetesNodePoolProperties) SetTaints(v []KubernetesNodePoolTaint) { + + o.Taints = &v + +} + +// HasTaints returns a boolean if a field has been set. +func (o *KubernetesNodePoolProperties) HasTaints() bool { + if o != nil && o.Taints != nil { + return true + } + + return false +} + // GetPublicIps returns the PublicIps field value // If the value is explicit nil, nil is returned func (o *KubernetesNodePoolProperties) GetPublicIps() *[]string { @@ -832,6 +872,10 @@ func (o KubernetesNodePoolProperties) MarshalJSON() ([]byte, error) { toSerialize["annotations"] = o.Annotations } + if o.Taints != nil { + toSerialize["taints"] = o.Taints + } + if o.PublicIps != nil { toSerialize["publicIps"] = o.PublicIps } diff --git a/vendor/github.com/ionos-cloud/sdk-go/v6/model_kubernetes_node_pool_properties_for_post.go b/vendor/github.com/ionos-cloud/sdk-go/v6/model_kubernetes_node_pool_properties_for_post.go index bed83e6200..db73e37653 100644 --- a/vendor/github.com/ionos-cloud/sdk-go/v6/model_kubernetes_node_pool_properties_for_post.go +++ b/vendor/github.com/ionos-cloud/sdk-go/v6/model_kubernetes_node_pool_properties_for_post.go @@ -45,6 +45,8 @@ type KubernetesNodePoolPropertiesForPost struct { Labels *map[string]string `json:"labels,omitempty"` // The annotations attached to the node pool. Annotations *map[string]string `json:"annotations,omitempty"` + // Taints applied to nodes in this pool. A taint repels pods that do not have a matching toleration. Maximum 50 taints per node pool. + Taints *[]KubernetesNodePoolTaint `json:"taints,omitempty"` // Optional array of reserved public IP addresses to be used by the nodes. The IPs must be from the exact location of the node pool's data center. If autoscaling is used, the array must contain one more IP than the maximum possible number of nodes (nodeCount+1 for a fixed number of nodes or maxNodeCount+1). The extra IP is used when the nodes are rebuilt. PublicIps *[]string `json:"publicIps,omitempty"` } @@ -688,6 +690,44 @@ func (o *KubernetesNodePoolPropertiesForPost) HasAnnotations() bool { return false } +// GetTaints returns the Taints field value +// If the value is explicit nil, nil is returned +func (o *KubernetesNodePoolPropertiesForPost) GetTaints() *[]KubernetesNodePoolTaint { + if o == nil { + return nil + } + + return o.Taints + +} + +// GetTaintsOk returns a tuple with the Taints field value +// and a boolean to check if the value has been set. +// NOTE: If the value is an explicit nil, `nil, true` will be returned +func (o *KubernetesNodePoolPropertiesForPost) GetTaintsOk() (*[]KubernetesNodePoolTaint, bool) { + if o == nil { + return nil, false + } + + return o.Taints, true +} + +// SetTaints sets field value +func (o *KubernetesNodePoolPropertiesForPost) SetTaints(v []KubernetesNodePoolTaint) { + + o.Taints = &v + +} + +// HasTaints returns a boolean if a field has been set. +func (o *KubernetesNodePoolPropertiesForPost) HasTaints() bool { + if o != nil && o.Taints != nil { + return true + } + + return false +} + // GetPublicIps returns the PublicIps field value // If the value is explicit nil, nil is returned func (o *KubernetesNodePoolPropertiesForPost) GetPublicIps() *[]string { @@ -792,6 +832,10 @@ func (o KubernetesNodePoolPropertiesForPost) MarshalJSON() ([]byte, error) { toSerialize["annotations"] = o.Annotations } + if o.Taints != nil { + toSerialize["taints"] = o.Taints + } + if o.PublicIps != nil { toSerialize["publicIps"] = o.PublicIps } diff --git a/vendor/github.com/ionos-cloud/sdk-go/v6/model_kubernetes_node_pool_properties_for_put.go b/vendor/github.com/ionos-cloud/sdk-go/v6/model_kubernetes_node_pool_properties_for_put.go index ce91a06255..67ff9fbe16 100644 --- a/vendor/github.com/ionos-cloud/sdk-go/v6/model_kubernetes_node_pool_properties_for_put.go +++ b/vendor/github.com/ionos-cloud/sdk-go/v6/model_kubernetes_node_pool_properties_for_put.go @@ -31,6 +31,8 @@ type KubernetesNodePoolPropertiesForPut struct { Labels *map[string]string `json:"labels,omitempty"` // The annotations attached to the node pool. Annotations *map[string]string `json:"annotations,omitempty"` + // Taints applied to nodes in this pool. A taint repels pods that do not have a matching toleration. Maximum 50 taints per node pool. + Taints *[]KubernetesNodePoolTaint `json:"taints,omitempty"` // Optional array of reserved public IP addresses to be used by the nodes. The IPs must be from the exact location of the node pool's data center. If autoscaling is used, the array must contain one more IP than the maximum possible number of nodes (nodeCount+1 for a fixed number of nodes or maxNodeCount+1). The extra IP is used when the nodes are rebuilt. PublicIps *[]string `json:"publicIps,omitempty"` } @@ -401,6 +403,44 @@ func (o *KubernetesNodePoolPropertiesForPut) HasAnnotations() bool { return false } +// GetTaints returns the Taints field value +// If the value is explicit nil, nil is returned +func (o *KubernetesNodePoolPropertiesForPut) GetTaints() *[]KubernetesNodePoolTaint { + if o == nil { + return nil + } + + return o.Taints + +} + +// GetTaintsOk returns a tuple with the Taints field value +// and a boolean to check if the value has been set. +// NOTE: If the value is an explicit nil, `nil, true` will be returned +func (o *KubernetesNodePoolPropertiesForPut) GetTaintsOk() (*[]KubernetesNodePoolTaint, bool) { + if o == nil { + return nil, false + } + + return o.Taints, true +} + +// SetTaints sets field value +func (o *KubernetesNodePoolPropertiesForPut) SetTaints(v []KubernetesNodePoolTaint) { + + o.Taints = &v + +} + +// HasTaints returns a boolean if a field has been set. +func (o *KubernetesNodePoolPropertiesForPut) HasTaints() bool { + if o != nil && o.Taints != nil { + return true + } + + return false +} + // GetPublicIps returns the PublicIps field value // If the value is explicit nil, nil is returned func (o *KubernetesNodePoolPropertiesForPut) GetPublicIps() *[]string { @@ -477,6 +517,10 @@ func (o KubernetesNodePoolPropertiesForPut) MarshalJSON() ([]byte, error) { toSerialize["annotations"] = o.Annotations } + if o.Taints != nil { + toSerialize["taints"] = o.Taints + } + if o.PublicIps != nil { toSerialize["publicIps"] = o.PublicIps } diff --git a/vendor/github.com/ionos-cloud/sdk-go/v6/model_kubernetes_node_pool_taint.go b/vendor/github.com/ionos-cloud/sdk-go/v6/model_kubernetes_node_pool_taint.go new file mode 100644 index 0000000000..0b8245e071 --- /dev/null +++ b/vendor/github.com/ionos-cloud/sdk-go/v6/model_kubernetes_node_pool_taint.go @@ -0,0 +1,212 @@ +/* + * CLOUD API + * + * IONOS Enterprise-grade Infrastructure as a Service (IaaS) solutions can be managed through the Cloud API, in addition or as an alternative to the \"Data Center Designer\" (DCD) browser-based tool. Both methods employ consistent concepts and features, deliver similar power and flexibility, and can be used to perform a multitude of management tasks, including adding servers, volumes, configuring networks, and so on. + * + * API version: 6.0 + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package ionoscloud + +import ( + "encoding/json" +) + +// KubernetesNodePoolTaint Kubernetes taint applied to node pool nodes. Taints allow nodes to repel pods that do not have matching tolerations. +type KubernetesNodePoolTaint struct { + // Taint key. Must be a valid Kubernetes label key format. May include an optional prefix (DNS subdomain) followed by a slash. + Key *string `json:"key"` + // Optional taint value. Must be a valid Kubernetes label value format. + Value *string `json:"value,omitempty"` + Effect *TaintEffect `json:"effect"` +} + +// NewKubernetesNodePoolTaint instantiates a new KubernetesNodePoolTaint object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewKubernetesNodePoolTaint(key string, effect TaintEffect) *KubernetesNodePoolTaint { + this := KubernetesNodePoolTaint{} + + this.Key = &key + this.Effect = &effect + + return &this +} + +// NewKubernetesNodePoolTaintWithDefaults instantiates a new KubernetesNodePoolTaint object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewKubernetesNodePoolTaintWithDefaults() *KubernetesNodePoolTaint { + this := KubernetesNodePoolTaint{} + return &this +} + +// GetKey returns the Key field value +// If the value is explicit nil, nil is returned +func (o *KubernetesNodePoolTaint) GetKey() *string { + if o == nil { + return nil + } + + return o.Key + +} + +// GetKeyOk returns a tuple with the Key field value +// and a boolean to check if the value has been set. +// NOTE: If the value is an explicit nil, `nil, true` will be returned +func (o *KubernetesNodePoolTaint) GetKeyOk() (*string, bool) { + if o == nil { + return nil, false + } + + return o.Key, true +} + +// SetKey sets field value +func (o *KubernetesNodePoolTaint) SetKey(v string) { + + o.Key = &v + +} + +// HasKey returns a boolean if a field has been set. +func (o *KubernetesNodePoolTaint) HasKey() bool { + if o != nil && o.Key != nil { + return true + } + + return false +} + +// GetValue returns the Value field value +// If the value is explicit nil, nil is returned +func (o *KubernetesNodePoolTaint) GetValue() *string { + if o == nil { + return nil + } + + return o.Value + +} + +// GetValueOk returns a tuple with the Value field value +// and a boolean to check if the value has been set. +// NOTE: If the value is an explicit nil, `nil, true` will be returned +func (o *KubernetesNodePoolTaint) GetValueOk() (*string, bool) { + if o == nil { + return nil, false + } + + return o.Value, true +} + +// SetValue sets field value +func (o *KubernetesNodePoolTaint) SetValue(v string) { + + o.Value = &v + +} + +// HasValue returns a boolean if a field has been set. +func (o *KubernetesNodePoolTaint) HasValue() bool { + if o != nil && o.Value != nil { + return true + } + + return false +} + +// GetEffect returns the Effect field value +// If the value is explicit nil, nil is returned +func (o *KubernetesNodePoolTaint) GetEffect() *TaintEffect { + if o == nil { + return nil + } + + return o.Effect + +} + +// GetEffectOk returns a tuple with the Effect field value +// and a boolean to check if the value has been set. +// NOTE: If the value is an explicit nil, `nil, true` will be returned +func (o *KubernetesNodePoolTaint) GetEffectOk() (*TaintEffect, bool) { + if o == nil { + return nil, false + } + + return o.Effect, true +} + +// SetEffect sets field value +func (o *KubernetesNodePoolTaint) SetEffect(v TaintEffect) { + + o.Effect = &v + +} + +// HasEffect returns a boolean if a field has been set. +func (o *KubernetesNodePoolTaint) HasEffect() bool { + if o != nil && o.Effect != nil { + return true + } + + return false +} + +func (o KubernetesNodePoolTaint) MarshalJSON() ([]byte, error) { + toSerialize := map[string]interface{}{} + if o.Key != nil { + toSerialize["key"] = o.Key + } + + if o.Value != nil { + toSerialize["value"] = o.Value + } + + if o.Effect != nil { + toSerialize["effect"] = o.Effect + } + + return json.Marshal(toSerialize) +} + +type NullableKubernetesNodePoolTaint struct { + value *KubernetesNodePoolTaint + isSet bool +} + +func (v NullableKubernetesNodePoolTaint) Get() *KubernetesNodePoolTaint { + return v.value +} + +func (v *NullableKubernetesNodePoolTaint) Set(val *KubernetesNodePoolTaint) { + v.value = val + v.isSet = true +} + +func (v NullableKubernetesNodePoolTaint) IsSet() bool { + return v.isSet +} + +func (v *NullableKubernetesNodePoolTaint) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableKubernetesNodePoolTaint(val *KubernetesNodePoolTaint) *NullableKubernetesNodePoolTaint { + return &NullableKubernetesNodePoolTaint{value: val, isSet: true} +} + +func (v NullableKubernetesNodePoolTaint) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableKubernetesNodePoolTaint) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/vendor/github.com/ionos-cloud/sdk-go/v6/model_location_properties.go b/vendor/github.com/ionos-cloud/sdk-go/v6/model_location_properties.go index c8ca11d88b..3c567b3a5b 100644 --- a/vendor/github.com/ionos-cloud/sdk-go/v6/model_location_properties.go +++ b/vendor/github.com/ionos-cloud/sdk-go/v6/model_location_properties.go @@ -18,6 +18,8 @@ import ( type LocationProperties struct { // The location name. Name *string `json:"name,omitempty"` + // the metro region, to which this datacenter location belongs to + MetroRegion *string `json:"metroRegion,omitempty"` // A list of available features in the location. Features *[]string `json:"features,omitempty"` // A list of image aliases available in the location. @@ -84,6 +86,44 @@ func (o *LocationProperties) HasName() bool { return false } +// GetMetroRegion returns the MetroRegion field value +// If the value is explicit nil, nil is returned +func (o *LocationProperties) GetMetroRegion() *string { + if o == nil { + return nil + } + + return o.MetroRegion + +} + +// GetMetroRegionOk returns a tuple with the MetroRegion field value +// and a boolean to check if the value has been set. +// NOTE: If the value is an explicit nil, `nil, true` will be returned +func (o *LocationProperties) GetMetroRegionOk() (*string, bool) { + if o == nil { + return nil, false + } + + return o.MetroRegion, true +} + +// SetMetroRegion sets field value +func (o *LocationProperties) SetMetroRegion(v string) { + + o.MetroRegion = &v + +} + +// HasMetroRegion returns a boolean if a field has been set. +func (o *LocationProperties) HasMetroRegion() bool { + if o != nil && o.MetroRegion != nil { + return true + } + + return false +} + // GetFeatures returns the Features field value // If the value is explicit nil, nil is returned func (o *LocationProperties) GetFeatures() *[]string { @@ -242,6 +282,10 @@ func (o LocationProperties) MarshalJSON() ([]byte, error) { toSerialize["name"] = o.Name } + if o.MetroRegion != nil { + toSerialize["metroRegion"] = o.MetroRegion + } + if o.Features != nil { toSerialize["features"] = o.Features } diff --git a/vendor/github.com/ionos-cloud/sdk-go/v6/model_server_properties.go b/vendor/github.com/ionos-cloud/sdk-go/v6/model_server_properties.go index 4022d16785..c1f3bfd30a 100644 --- a/vendor/github.com/ionos-cloud/sdk-go/v6/model_server_properties.go +++ b/vendor/github.com/ionos-cloud/sdk-go/v6/model_server_properties.go @@ -22,20 +22,22 @@ type ServerProperties struct { Name *string `json:"name,omitempty"` // The hostname of the resource. Allowed characters are a-z, 0-9 and - (minus). Hostname should not start with minus and should not be longer than 63 characters. Hostname *string `json:"hostname,omitempty"` - // The total number of cores for the server. It can not be supplied for the VMs that have to be created based on templates. + // The total number of cores for the server. It can not be supplied for the VMs that have to be created based on templates. For servers with Confidential Computing enabled, the number of cores must match the amount of cores required by the Confidential Computing image, and this field is immutable once the server has been created — update requests attempting to change it will be rejected. Cores *int32 `json:"cores,omitempty"` - // The memory size for the enterprise server in MB, such as 2048. Size must be specified in multiples of 256 MB with a minimum of 256 MB; however, if you set ramHotPlug to TRUE then you must use a minimum of 1024 MB. If you set the RAM size more than 240GB, then ramHotPlug will be set to FALSE and can not be set to TRUE unless RAM size not set to less than 240GB. It can not be supplied for the VMs that have to be created based on templates. + // The memory size for the server in MB, such as 2048. Size must be specified in multiples of 256 MB with a minimum of 256 MB; however, if you set ramHotPlug to TRUE then you must use a minimum of 1024 MB. If you set the RAM size more than 240GB, then ramHotPlug will be set to FALSE and can not be set to TRUE unless RAM size not set to less than 240GB. It can not be supplied for the VMs that have to be created based on templates. For servers with Confidential Computing enabled, this field is immutable once the server has been created — update requests attempting to change it will be rejected. Ram *int32 `json:"ram,omitempty"` - // The availability zone in which the server should be provisioned. For CUBE and GPU servers, the only value accepted is 'AUTO' + // The availability zone in which the server should be provisioned. For CUBE and GPU servers, the only value accepted is 'AUTO'. For servers with Confidential Computing enabled, this field is immutable once the server has been created — update requests attempting to change it will be rejected. AvailabilityZone *string `json:"availabilityZone,omitempty"` // Status of the virtual machine. VmState *string `json:"vmState,omitempty"` BootCdrom *ResourceReference `json:"bootCdrom,omitempty"` BootVolume *ResourceReference `json:"bootVolume,omitempty"` - // CPU architecture on which server gets provisioned; not all CPU architectures are available in all datacenter regions; available CPU architectures can be retrieved from the datacenter resource; must not be provided for CUBE and VCPU servers. + // CPU architecture on which server gets provisioned; not all CPU architectures are available in all datacenter regions; available CPU architectures can be retrieved from the datacenter resource; must not be provided for CUBE and VCPU servers. If the field is omitted from the request or the value is empty or null, an available CPU architecture will be automatically selected. This field must not be supplied when creating a server with Confidential Computing enabled (i.e. when one of the attached volumes uses a confidential computing image); in that case the CPU family is determined by the image and is selected automatically. On servers with Confidential Computing enabled this field is also immutable — update requests attempting to change it will be rejected. CpuFamily *string `json:"cpuFamily,omitempty"` - // Server type: CUBE, ENTERPRISE, VCPU or GPU. + // Server type: CUBE, ENTERPRISE, VCPU or GPU. Confidential Computing can be enabled only on a server of type ENTERPRISE by creating it with a volume with a Confidential Computing image. Type *string `json:"type,omitempty"` + // The list of features enabled on this server. An ENTERPRISE server with Confidential Computing enabled will have `SEV-SNP` as part of this list. + EnabledFeatures *[]string `json:"enabledFeatures,omitempty"` // The placement group ID that belongs to this server; Requires system privileges, for internal usage only PlacementGroupId *string `json:"placementGroupId,omitempty"` // Activate or deactivate the Multi Queue feature on all NICs of this server. This feature is beneficial to enable when the NICs are experiencing performance issues (e.g. low throughput). Toggling this feature will also initiate a restart of the server. If the specified value is `true`, the feature will be activated; if it is not specified or set to `false`, the feature will be deactivated. It is not allowed for servers of type Cube. @@ -478,6 +480,44 @@ func (o *ServerProperties) HasType() bool { return false } +// GetEnabledFeatures returns the EnabledFeatures field value +// If the value is explicit nil, nil is returned +func (o *ServerProperties) GetEnabledFeatures() *[]string { + if o == nil { + return nil + } + + return o.EnabledFeatures + +} + +// GetEnabledFeaturesOk returns a tuple with the EnabledFeatures field value +// and a boolean to check if the value has been set. +// NOTE: If the value is an explicit nil, `nil, true` will be returned +func (o *ServerProperties) GetEnabledFeaturesOk() (*[]string, bool) { + if o == nil { + return nil, false + } + + return o.EnabledFeatures, true +} + +// SetEnabledFeatures sets field value +func (o *ServerProperties) SetEnabledFeatures(v []string) { + + o.EnabledFeatures = &v + +} + +// HasEnabledFeatures returns a boolean if a field has been set. +func (o *ServerProperties) HasEnabledFeatures() bool { + if o != nil && o.EnabledFeatures != nil { + return true + } + + return false +} + // GetPlacementGroupId returns the PlacementGroupId field value // If the value is explicit nil, nil is returned func (o *ServerProperties) GetPlacementGroupId() *string { @@ -600,6 +640,10 @@ func (o ServerProperties) MarshalJSON() ([]byte, error) { toSerialize["type"] = o.Type } + if o.EnabledFeatures != nil { + toSerialize["enabledFeatures"] = o.EnabledFeatures + } + if o.PlacementGroupId != nil { toSerialize["placementGroupId"] = o.PlacementGroupId } diff --git a/vendor/github.com/ionos-cloud/sdk-go/v6/model_taint_effect.go b/vendor/github.com/ionos-cloud/sdk-go/v6/model_taint_effect.go new file mode 100644 index 0000000000..fae56d8fb7 --- /dev/null +++ b/vendor/github.com/ionos-cloud/sdk-go/v6/model_taint_effect.go @@ -0,0 +1,84 @@ +/* + * CLOUD API + * + * IONOS Enterprise-grade Infrastructure as a Service (IaaS) solutions can be managed through the Cloud API, in addition or as an alternative to the \"Data Center Designer\" (DCD) browser-based tool. Both methods employ consistent concepts and features, deliver similar power and flexibility, and can be used to perform a multitude of management tasks, including adding servers, volumes, configuring networks, and so on. + * + * API version: 6.0 + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package ionoscloud + +import ( + "encoding/json" + "fmt" +) + +// TaintEffect Taint effect determines how a taint repels pods: * NoSchedule: Pods without matching toleration will not be scheduled on the node. * NoExecute: Pods without matching toleration will be evicted from the node. * PreferNoSchedule: Kubernetes will try to avoid scheduling pods without matching toleration. +type TaintEffect string + +// List of TaintEffect +const ( + NO_SCHEDULE TaintEffect = "NoSchedule" + NO_EXECUTE TaintEffect = "NoExecute" + PREFER_NO_SCHEDULE TaintEffect = "PreferNoSchedule" +) + +func (v *TaintEffect) UnmarshalJSON(src []byte) error { + var value string + err := json.Unmarshal(src, &value) + if err != nil { + return err + } + enumTypeValue := TaintEffect(value) + for _, existing := range []TaintEffect{"NoSchedule", "NoExecute", "PreferNoSchedule"} { + if existing == enumTypeValue { + *v = enumTypeValue + return nil + } + } + + return fmt.Errorf("%+v is not a valid TaintEffect", value) +} + +// Ptr returns reference to TaintEffect value +func (v TaintEffect) Ptr() *TaintEffect { + return &v +} + +type NullableTaintEffect struct { + value *TaintEffect + isSet bool +} + +func (v NullableTaintEffect) Get() *TaintEffect { + return v.value +} + +func (v *NullableTaintEffect) Set(val *TaintEffect) { + v.value = val + v.isSet = true +} + +func (v NullableTaintEffect) IsSet() bool { + return v.isSet +} + +func (v *NullableTaintEffect) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableTaintEffect(val *TaintEffect) *NullableTaintEffect { + return &NullableTaintEffect{value: val, isSet: true} +} + +func (v NullableTaintEffect) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableTaintEffect) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/vendor/github.com/ionos-cloud/sdk-go/v6/model_template_properties.go b/vendor/github.com/ionos-cloud/sdk-go/v6/model_template_properties.go index 1f66597c81..9e8aa0dac3 100644 --- a/vendor/github.com/ionos-cloud/sdk-go/v6/model_template_properties.go +++ b/vendor/github.com/ionos-cloud/sdk-go/v6/model_template_properties.go @@ -24,6 +24,8 @@ type TemplateProperties struct { Ram *float32 `json:"ram"` // The storage size in GB. StorageSize *float32 `json:"storageSize"` + // The performance class type for the storage. The only possible value is 'SSD Premium' + StorageType *string `json:"storageType,omitempty"` // The description of the template. Category *string `json:"category"` // List of GPUs assigned to the template @@ -206,6 +208,44 @@ func (o *TemplateProperties) HasStorageSize() bool { return false } +// GetStorageType returns the StorageType field value +// If the value is explicit nil, nil is returned +func (o *TemplateProperties) GetStorageType() *string { + if o == nil { + return nil + } + + return o.StorageType + +} + +// GetStorageTypeOk returns a tuple with the StorageType field value +// and a boolean to check if the value has been set. +// NOTE: If the value is an explicit nil, `nil, true` will be returned +func (o *TemplateProperties) GetStorageTypeOk() (*string, bool) { + if o == nil { + return nil, false + } + + return o.StorageType, true +} + +// SetStorageType sets field value +func (o *TemplateProperties) SetStorageType(v string) { + + o.StorageType = &v + +} + +// HasStorageType returns a boolean if a field has been set. +func (o *TemplateProperties) HasStorageType() bool { + if o != nil && o.StorageType != nil { + return true + } + + return false +} + // GetCategory returns the Category field value // If the value is explicit nil, nil is returned func (o *TemplateProperties) GetCategory() *string { @@ -300,6 +340,10 @@ func (o TemplateProperties) MarshalJSON() ([]byte, error) { toSerialize["storageSize"] = o.StorageSize } + if o.StorageType != nil { + toSerialize["storageType"] = o.StorageType + } + if o.Category != nil { toSerialize["category"] = o.Category } diff --git a/vendor/github.com/ionos-cloud/sdk-go/v6/model_volume_properties.go b/vendor/github.com/ionos-cloud/sdk-go/v6/model_volume_properties.go index c2b0c470ca..57c82d341d 100644 --- a/vendor/github.com/ionos-cloud/sdk-go/v6/model_volume_properties.go +++ b/vendor/github.com/ionos-cloud/sdk-go/v6/model_volume_properties.go @@ -24,11 +24,11 @@ type VolumeProperties struct { Size *float32 `json:"size,omitempty"` // The availability zone in which the volume should be provisioned. The storage volume will be provisioned on as few physical storage devices as possible, but this cannot be guaranteed upfront. This is uavailable for DAS (Direct Attached Storage), and subject to availability for SSD. AvailabilityZone *string `json:"availabilityZone,omitempty"` - // Image or snapshot ID to be used as template for this volume. MSSQL Enterprise Images can be used only if the feature toggle for MSSQL Enterprise is enabled on the contract. + // Image or snapshot ID to be used as template for this volume. MSSQL Enterprise Images can be used only if the feature toggle for MSSQL Enterprise is enabled on the contract. If the referenced image supports Confidential Computing, additional rules apply: the volume can only be created inline as part of a server creation request (not via standalone volume creation, nor by attaching to an existing server), and exactly one volume on that server may use such an image. The resulting volume is permanently bound to the server as its boot device and cannot be detached or moved to another server. Image *string `json:"image,omitempty"` // Initial password to be set for installed OS. Works with public images only. Not modifiable, forbidden in update requests. Password rules allows all characters from a-z, A-Z, 0-9. ImagePassword *string `json:"imagePassword,omitempty"` - // Image alias of an image to be used as template for this volume. MSSQL Enterprise Images can be used only if the feature toggle for MSSQL Enterprise is enabled on the contract. + // Image alias of an image to be used as template for this volume. MSSQL Enterprise Images can be used only if the feature toggle for MSSQL Enterprise is enabled on the contract. If the referenced image supports Confidential Computing, additional rules apply: the volume can only be created inline as part of a server creation request (not via standalone volume creation, nor by attaching to an existing server), and exactly one volume on that server may use such an image. The resulting volume is permanently bound to the server as its boot device and cannot be detached or moved to another server. ImageAlias *string `json:"imageAlias,omitempty"` // Public SSH keys are set on the image as authorized keys for appropriate SSH login to the instance using the corresponding private key. This field may only be set in creation requests. When reading, it always returns null. SSH keys are only supported if a public Linux image is used for the volume creation. SshKeys *[]string `json:"sshKeys,omitempty"` diff --git a/vendor/github.com/ionos-cloud/sdk-go/v6/response.go b/vendor/github.com/ionos-cloud/sdk-go/v6/response.go index 0d6a2c9087..7886dcfb82 100644 --- a/vendor/github.com/ionos-cloud/sdk-go/v6/response.go +++ b/vendor/github.com/ionos-cloud/sdk-go/v6/response.go @@ -13,6 +13,7 @@ package ionoscloud import ( "log" "net/http" + "net/url" "time" ) @@ -60,6 +61,24 @@ func (resp *APIResponse) HttpNotFound() bool { return false } +// SafeStatusCode returns the HTTP status code from the embedded *http.Response. +// Returns 0 if the receiver or the embedded response is nil. +func (resp *APIResponse) SafeStatusCode() int { + if resp == nil || resp.Response == nil { + return 0 + } + return resp.Response.StatusCode +} + +// SafeLocation returns the Location header as a *url.URL. +// Returns nil, nil if the receiver or the embedded response is nil. +func (resp *APIResponse) SafeLocation() (*url.URL, error) { + if resp == nil || resp.Response == nil { + return nil, nil + } + return resp.Response.Location() +} + // LogInfo - logs APIResponse values like RequestTime, Operation and StatusCode // does not print anything for nil APIResponse values func (resp *APIResponse) LogInfo() { diff --git a/vendor/modules.txt b/vendor/modules.txt index c25a58e334..93b4dd250c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -129,7 +129,7 @@ github.com/ionos-cloud/sdk-go-cert-manager # github.com/ionos-cloud/sdk-go-vm-autoscaling v1.1.1 ## explicit; go 1.18 github.com/ionos-cloud/sdk-go-vm-autoscaling -# github.com/ionos-cloud/sdk-go/v6 v6.3.6 +# github.com/ionos-cloud/sdk-go/v6 v6.3.11 ## explicit; go 1.24 github.com/ionos-cloud/sdk-go/v6 # github.com/ionoscloudsdk/comptplus v1.1.4