diff --git a/docs/stackit_server_create.md b/docs/stackit_server_create.md index c1c4e9b21..d807bcc3c 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-provisioning-policy ALWAYS ``` ### Options ``` --affinity-group string The affinity group the server is assigned to + --agent-provisioning-policy string Whether to provision an agent on server creation, one of ["ALWAYS" "NEVER" "INHERIT"] (default "INHERIT") --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..5960a6bb7 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" + agentProvisioningPolicyFlag = "agent-provisioning-policy" 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 + AgentProvisioningPolicy *string 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-provisioning-policy ALWAYS`, + ), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -162,6 +168,8 @@ func NewCmd(params *types.CmdParams) *cobra.Command { } func configureFlags(cmd *cobra.Command) { + agentProvisioningPolicyOptions := []string{"ALWAYS", "NEVER", "INHERIT"} + cmd.Flags().Var(flags.EnumFlag(false, "INHERIT", agentProvisioningPolicyOptions...), agentProvisioningPolicyFlag, fmt.Sprintf("Whether to provision an agent on server creation, one of %q", agentProvisioningPolicyOptions)) cmd.Flags().StringP(nameFlag, "n", "", "Server name") 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") @@ -250,6 +258,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), + AgentProvisioningPolicy: flags.FlagToStringPointer(p, cmd, agentProvisioningPolicyFlag), BootVolumeSourceId: flags.FlagToStringPointer(p, cmd, bootVolumeSourceIdFlag), BootVolumeSourceType: flags.FlagToStringPointer(p, cmd, bootVolumeSourceTypeFlag), BootVolumeSize: flags.FlagToInt64Pointer(p, cmd, bootVolumeSizeFlag), @@ -293,6 +302,15 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APICli Labels: utils.ConvertStringMapToInterfaceMap(model.Labels), } + if model.AgentProvisioningPolicy != nil { + switch *model.AgentProvisioningPolicy { + case "ALWAYS": + payload.Agent = &iaas.ServerAgent{Provisioned: utils.Ptr(true)} + case "NEVER": + payload.Agent = &iaas.ServerAgent{Provisioned: utils.Ptr(false)} + } + } + 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..d28ea513a 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, + agentProvisioningPolicyFlag: "INHERIT", 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, }, + AgentProvisioningPolicy: utils.Ptr("INHERIT"), AvailabilityZone: utils.Ptr("eu01-1"), Name: utils.Ptr("test-server-name"), MachineType: utils.Ptr("t1.1"), @@ -164,6 +166,7 @@ func TestParseInput(t *testing.T) { description: "required only", flagValues: fixtureFlagValues(func(flagValues map[string]string) { delete(flagValues, affinityGroupFlag) + delete(flagValues, agentProvisioningPolicyFlag) delete(flagValues, availabilityZoneFlag) delete(flagValues, labelFlag) delete(flagValues, bootVolumeSourceIdFlag) @@ -182,6 +185,7 @@ func TestParseInput(t *testing.T) { isValid: true, expectedModel: fixtureInputModel(func(model *inputModel) { model.AffinityGroup = nil + model.AgentProvisioningPolicy = nil model.AvailabilityZone = nil model.Labels = nil model.BootVolumeSourceId = nil @@ -327,6 +331,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, agentProvisioningPolicyFlag) + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.AgentProvisioningPolicy = nil + }), + }, + { + description: "agent-provisioned flag properly handled", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[agentProvisioningPolicyFlag] = "ALWAYS" + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.AgentProvisioningPolicy = utils.Ptr("ALWAYS") + }), + }, } for _, tt := range tests { @@ -360,6 +384,19 @@ func TestBuildRequest(t *testing.T) { }, expectedRequest: fixtureRequiredRequest(), }, + { + description: "with provisioned agent", + model: fixtureInputModel(func(model *inputModel) { + model.AgentProvisioningPolicy = utils.Ptr("ALWAYS") + }), + expectedRequest: fixtureRequest(func(request *iaas.ApiCreateServerRequest) { + payload := fixturePayload() + payload.Agent = &iaas.ServerAgent{ + Provisioned: utils.Ptr(true), + } + *request = (*request).CreateServerPayload(payload) + }), + }, } 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 594f411d3..f4e8a34fb 100644 --- a/internal/pkg/utils/utils.go +++ b/internal/pkg/utils/utils.go @@ -201,6 +201,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 @@ -241,6 +242,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 03b270309..484efcec8 100644 --- a/internal/pkg/utils/utils_test.go +++ b/internal/pkg/utils/utils_test.go @@ -349,6 +349,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 {