From e5c2935e085d55f7862026d47f1c8f2eb5b16484 Mon Sep 17 00:00:00 2001 From: Adrian Nackov Date: Thu, 8 Jan 2026 08:38:44 +0000 Subject: [PATCH] STACKITRCO-186 - Add flag: iaas API param agent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a cli flag for the iaas ( _create server_ ) API param: `"agent": {"provisioned": true}` ref STACKITRCO-186 --- Tests: * ran `make fmt`, `make generate-docs` * unit tests ``` [~/stackit-cli] go test internal/cmd/server/create/* ok command-line-arguments 0.006s [~/stackit-cli] [~/stackit-cli] make test >> Running tests for the CLI application ... ok github.com/stackitcloud/stackit-cli/internal/cmd/server/create 0.006s coverage: 69.7% of statements ... ``` * ran without specifying the new flag (default is agent-provisioned=false) - no change, same behavior as before ``` [~/stackit-cli] stackit -y --project-id c904f41c-2f8c-4edb-b966-e87d65f10b64 server create --name server1 --machine-type t1.1 --network-id 97c5dde4-cb9d-49b8-be55-9cdf0c3795e1 --boot-volume-source-type image --boot-volume-source-id 21466190-b904-4267-8bf3-1be4323f4ffb --boot-volume-size 20 --boot-volume-delete-on-termination=true ... Server ID: 71785201-d749-4449-99b2-6dc23f406133 [~/stackit-cli] stackit -y server command create --server-id=71785201-d749-4449-99b2-6dc23f406133 --project-id=c904f41c-2f8c-4edb-b966-e87d65f10b64 --template-name=RunShellScript --params script='echo hello' Error: create Server Command: 404 Not Found, status code 404, Body: {"status":"Not Found","message":"agent not found"} [~/stackit-cli] stackit -y --project-id c904f41c-2f8c-4edb-b966-e87d65f10b64 server delete 71785201-d749-4449-99b2-6dc23f406133 Deleting server ✓ Deleted server "server1" ``` * ran with specifying flag agent-provisioned=true - the server was created with a provisioned agent, it was possible to set commands ``` [~/stackit-cli] stackit -y --project-id c904f41c-2f8c-4edb-b966-e87d65f10b64 server create --name server1 --machine-type t1.1 --network-id 97c5dde4-cb9d-49b8-be55-9cdf0c3795e1 --boot-volume-source-type image --boot-volume-source-id 21466190-b904-4267-8bf3-1be4323f4ffb --boot-volume-size 20 --boot-volume-delete-on-termination=true --agent-provisioned=true ... Server ID: ed3086ff-a1ef-44ec-b2f7-08775611dc4e [~/stackit-cli] stackit -y server command create --server-id=ed3086ff-a1ef-44ec-b2f7-08775611dc4e --project-id=c904f41c-2f8c-4edb-b966-e87d65f10b64 --template-name=RunShellScript --params script='echo hello' Created server command for server server1. Command ID: 263667 [~/stackit-cli] stackit -y --project-id c904f41c-2f8c-4edb-b966-e87d65f10b64 server delete ed3086ff-a1ef-44ec-b2f7-08775611dc4e Deleting server ✓ Deleted server "server1" ``` Signed-off-by: Adrian Nackov --- docs/stackit_server_create.md | 4 +++ internal/cmd/server/create/create.go | 14 ++++++++++ internal/cmd/server/create/create_test.go | 31 +++++++++++++++++++++-- internal/cmd/server/describe/describe.go | 5 ++++ internal/cmd/server/list/list.go | 8 +++++- internal/pkg/utils/utils.go | 2 ++ internal/pkg/utils/utils_test.go | 21 +++++++++++++++ 7 files changed, 82 insertions(+), 3 deletions(-) diff --git a/docs/stackit_server_create.md b/docs/stackit_server_create.md index c1c4e9b21..55e34f91f 100644 --- a/docs/stackit_server_create.md +++ b/docs/stackit_server_create.md @@ -39,12 +39,16 @@ stackit server create [flags] Create a server with user data (cloud-init) $ stackit server create --machine-type t1.1 --name server1 --boot-volume-source-id xxx --boot-volume-source-type image --boot-volume-size 64 --user-data @path/to/file.yaml + + Create a server with provisioned agent + $ stackit server create --machine-type t1.1 --name server1 --boot-volume-source-id xxx --boot-volume-source-type image --boot-volume-size 64 --network-id yyy --agent-provisioned=true ``` ### Options ``` --affinity-group string The affinity group the server is assigned to + --agent-provisioned Whether to provision an agent on server creation --availability-zone string The availability zone of the server --boot-volume-delete-on-termination Delete the volume during the termination of the server. Defaults to false --boot-volume-performance-class string Boot volume performance class diff --git a/internal/cmd/server/create/create.go b/internal/cmd/server/create/create.go index 1efaac4f4..8b1f56e52 100644 --- a/internal/cmd/server/create/create.go +++ b/internal/cmd/server/create/create.go @@ -28,6 +28,7 @@ const ( machineTypeFlag = "machine-type" affinityGroupFlag = "affinity-group" availabilityZoneFlag = "availability-zone" + agentProvisionedFlag = "agent-provisioned" bootVolumeSourceIdFlag = "boot-volume-source-id" bootVolumeSourceTypeFlag = "boot-volume-source-type" bootVolumeSizeFlag = "boot-volume-size" @@ -50,6 +51,7 @@ type inputModel struct { MachineType *string AffinityGroup *string AvailabilityZone *string + AgentProvisioned *bool BootVolumeSourceId *string BootVolumeSourceType *string BootVolumeSize *int64 @@ -109,6 +111,10 @@ func NewCmd(params *types.CmdParams) *cobra.Command { `Create a server with user data (cloud-init)`, `$ stackit server create --machine-type t1.1 --name server1 --boot-volume-source-id xxx --boot-volume-source-type image --boot-volume-size 64 --user-data @path/to/file.yaml`, ), + examples.NewExample( + `Create a server with provisioned agent`, + `$ stackit server create --machine-type t1.1 --name server1 --boot-volume-source-id xxx --boot-volume-source-type image --boot-volume-size 64 --network-id yyy --agent-provisioned=true`, + ), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -166,6 +172,7 @@ func configureFlags(cmd *cobra.Command) { cmd.Flags().String(machineTypeFlag, "", "Name of the type of the machine for the server. Possible values are documented in https://docs.stackit.cloud/products/compute-engine/server/basics/machine-types/") cmd.Flags().String(affinityGroupFlag, "", "The affinity group the server is assigned to") cmd.Flags().String(availabilityZoneFlag, "", "The availability zone of the server") + cmd.Flags().Bool(agentProvisionedFlag, false, "Whether to provision an agent on server creation") cmd.Flags().String(bootVolumeSourceIdFlag, "", "ID of the source object of boot volume. It can be either an image or volume ID") cmd.Flags().String(bootVolumeSourceTypeFlag, "", "Type of the source object of boot volume. It can be either 'image' or 'volume'") cmd.Flags().Int64(bootVolumeSizeFlag, 0, "The size of the boot volume in GB. Must be provided when 'boot-volume-source-type' is 'image'") @@ -250,6 +257,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, MachineType: flags.FlagToStringPointer(p, cmd, machineTypeFlag), AffinityGroup: flags.FlagToStringPointer(p, cmd, affinityGroupFlag), AvailabilityZone: flags.FlagToStringPointer(p, cmd, availabilityZoneFlag), + AgentProvisioned: flags.FlagToBoolPointer(p, cmd, agentProvisionedFlag), BootVolumeSourceId: flags.FlagToStringPointer(p, cmd, bootVolumeSourceIdFlag), BootVolumeSourceType: flags.FlagToStringPointer(p, cmd, bootVolumeSourceTypeFlag), BootVolumeSize: flags.FlagToInt64Pointer(p, cmd, bootVolumeSizeFlag), @@ -293,6 +301,12 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APICli Labels: utils.ConvertStringMapToInterfaceMap(model.Labels), } + if model.AgentProvisioned != nil { + payload.Agent = &iaas.ServerAgent{ + Provisioned: model.AgentProvisioned, + } + } + if model.BootVolumePerformanceClass != nil || model.BootVolumeSize != nil || model.BootVolumeDeleteOnTermination != nil || model.BootVolumeSourceId != nil || model.BootVolumeSourceType != nil { payload.BootVolume = &iaas.ServerBootVolume{ PerformanceClass: model.BootVolumePerformanceClass, diff --git a/internal/cmd/server/create/create_test.go b/internal/cmd/server/create/create_test.go index 56eb262ce..2372b7a28 100644 --- a/internal/cmd/server/create/create_test.go +++ b/internal/cmd/server/create/create_test.go @@ -35,6 +35,7 @@ func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]st globalflags.ProjectIdFlag: testProjectId, globalflags.RegionFlag: testRegion, + agentProvisionedFlag: "false", availabilityZoneFlag: "eu01-1", nameFlag: "test-server-name", machineTypeFlag: "t1.1", @@ -65,6 +66,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { Region: testRegion, Verbosity: globalflags.VerbosityDefault, }, + AgentProvisioned: utils.Ptr(false), AvailabilityZone: utils.Ptr("eu01-1"), Name: utils.Ptr("test-server-name"), MachineType: utils.Ptr("t1.1"), @@ -116,8 +118,11 @@ func fixturePayload(mods ...func(payload *iaas.CreateServerPayload)) iaas.Create Labels: utils.Ptr(map[string]interface{}{ "key": "value", }), - MachineType: utils.Ptr("t1.1"), - Name: utils.Ptr("test-server-name"), + MachineType: utils.Ptr("t1.1"), + Name: utils.Ptr("test-server-name"), + Agent: &iaas.ServerAgent{ + Provisioned: utils.Ptr(false), + }, AvailabilityZone: utils.Ptr("eu01-1"), AffinityGroup: utils.Ptr("test-affinity-group"), KeypairName: utils.Ptr("test-keypair-name"), @@ -164,6 +169,7 @@ func TestParseInput(t *testing.T) { description: "required only", flagValues: fixtureFlagValues(func(flagValues map[string]string) { delete(flagValues, affinityGroupFlag) + delete(flagValues, agentProvisionedFlag) delete(flagValues, availabilityZoneFlag) delete(flagValues, labelFlag) delete(flagValues, bootVolumeSourceIdFlag) @@ -182,6 +188,7 @@ func TestParseInput(t *testing.T) { isValid: true, expectedModel: fixtureInputModel(func(model *inputModel) { model.AffinityGroup = nil + model.AgentProvisioned = nil model.AvailabilityZone = nil model.Labels = nil model.BootVolumeSourceId = nil @@ -327,6 +334,26 @@ func TestParseInput(t *testing.T) { model.ImageId = nil }), }, + { + description: "valid with agent-provisioned flag missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, agentProvisionedFlag) + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.AgentProvisioned = nil + }), + }, + { + description: "agent-provisioned flag properly handled", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[agentProvisionedFlag] = "true" + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.AgentProvisioned = utils.Ptr(true) + }), + }, } for _, tt := range tests { diff --git a/internal/cmd/server/describe/describe.go b/internal/cmd/server/describe/describe.go index f13e6f5c8..3d77219b5 100644 --- a/internal/cmd/server/describe/describe.go +++ b/internal/cmd/server/describe/describe.go @@ -161,6 +161,11 @@ func outputResult(p *print.Printer, outputFormat string, server *iaas.Server) er table.AddSeparator() } + if server.Agent != nil && server.Agent.Provisioned != nil { + table.AddRow("AGENT", *server.Agent.Provisioned) + table.AddSeparator() + } + if server.MachineType != nil { table.AddRow("MACHINE TYPE", *server.MachineType) table.AddSeparator() diff --git a/internal/cmd/server/list/list.go b/internal/cmd/server/list/list.go index 54c058dc0..e97ff5b2a 100644 --- a/internal/cmd/server/list/list.go +++ b/internal/cmd/server/list/list.go @@ -165,7 +165,7 @@ func outputResult(p *print.Printer, outputFormat string, servers []iaas.Server) return nil default: table := tables.NewTable() - table.SetHeader("ID", "Name", "Status", "Machine Type", "Availability Zones", "Nic IPv4", "Public IPs") + table.SetHeader("ID", "Name", "Status", "Machine Type", "Availability Zones", "Nic IPv4", "Public IPs", "Agent") for i := range servers { server := servers[i] @@ -186,6 +186,11 @@ func outputResult(p *print.Printer, outputFormat string, servers []iaas.Server) } } + agent := "" + if server.Agent != nil { + agent = utils.PtrString(server.Agent.Provisioned) + } + table.AddRow( utils.PtrString(server.Id), utils.PtrString(server.Name), @@ -194,6 +199,7 @@ func outputResult(p *print.Printer, outputFormat string, servers []iaas.Server) utils.PtrString(server.AvailabilityZone), nicIPv4, publicIPs, + agent, ) } diff --git a/internal/pkg/utils/utils.go b/internal/pkg/utils/utils.go index 0cb50a3d4..d4a6e0119 100644 --- a/internal/pkg/utils/utils.go +++ b/internal/pkg/utils/utils.go @@ -192,6 +192,7 @@ type Base64PatchedServer struct { UpdatedAt *time.Time `json:"updatedAt,omitempty"` UserData *Base64Bytes `json:"userData,omitempty"` Volumes *[]string `json:"volumes,omitempty"` + Agent *iaas.ServerAgent `json:"agent,omitempty"` } // ConvertToBase64PatchedServer converts an iaas.Server to Base64PatchedServer @@ -232,6 +233,7 @@ func ConvertToBase64PatchedServer(server *iaas.Server) *Base64PatchedServer { UpdatedAt: server.UpdatedAt, UserData: userData, Volumes: server.Volumes, + Agent: server.Agent, } } diff --git a/internal/pkg/utils/utils_test.go b/internal/pkg/utils/utils_test.go index 0b5a656af..b90674ee4 100644 --- a/internal/pkg/utils/utils_test.go +++ b/internal/pkg/utils/utils_test.go @@ -334,6 +334,27 @@ func TestConvertToBase64PatchedServer(t *testing.T) { UserData: nil, }, }, + { + name: "server with agent", + input: &iaas.Server{ + Id: Ptr("server-456"), + Name: Ptr("test-server-2"), + Status: Ptr("STOPPED"), + AvailabilityZone: Ptr("eu01-2"), + MachineType: Ptr("t1.2"), + UserData: &emptyUserData, + Agent: &iaas.ServerAgent{Provisioned: Ptr(true)}, + }, + expected: &Base64PatchedServer{ + Id: Ptr("server-456"), + Name: Ptr("test-server-2"), + Status: Ptr("STOPPED"), + AvailabilityZone: Ptr("eu01-2"), + MachineType: Ptr("t1.2"), + UserData: Ptr(Base64Bytes(emptyUserData)), + Agent: Ptr(iaas.ServerAgent{Provisioned: Ptr(true)}), + }, + }, } for _, tt := range tests {