From e9ec6c55012df4b53983cca7f6ffb8541b31cb62 Mon Sep 17 00:00:00 2001 From: Toni Kangas Date: Wed, 17 Jul 2024 14:48:24 +0300 Subject: [PATCH] feat(server): update default template to Ubuntu 24.04 (#322) Also enable metadata service by default when the selected (or default) template requires it. --- CHANGELOG.md | 5 ++ examples/create_and_ssh_into_a_server.md | 4 +- examples/possible_exit_codes.md | 8 +-- go.mod | 2 +- go.sum | 4 +- internal/commands/server/create.go | 31 ++++++--- internal/commands/server/create_test.go | 86 ++++++++++++++++++------ 7 files changed, 102 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0be215724..e11efcbc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- In `server create` command, use `Ubuntu Server 24.04 LTS (Noble Numbat)` as default value for `--os`. The new default template only supports SSH key based authentication. Use `--ssh-keys` option to provide the keys when creating a server with the default template. +- In `server create` command, enable metadata service by default when the selected (or default) template uses cloud-init (`template_type` is `cloud-init`) and thus requires it. + ## [3.9.0] - 2024-07-04 ### Added diff --git a/examples/create_and_ssh_into_a_server.md b/examples/create_and_ssh_into_a_server.md index 315221f69..29ad12a5f 100644 --- a/examples/create_and_ssh_into_a_server.md +++ b/examples/create_and_ssh_into_a_server.md @@ -12,7 +12,7 @@ In order to be able to connect to the server we are going to create, we will nee ```sh # Create ssh-key into current working directory -ssh-keygen -t ed25519 -q -f "./id_ed25519" -N "" +ssh-keygen -t ed25519 -q -f "./id_ed25519" -N "" -C "upctl example" ``` Create a server using the above created ssh-key as login method. @@ -34,7 +34,7 @@ Find the IP address of the created server from the JSON output of `upctl server ip=$(upctl server show ${prefix}server -o json | jq -r '.networking.interfaces[] | select(.type == "public") | .ip_addresses[0].address') # Wait for a moment for the ssh server to become available -sleep 15 +sleep 30 ssh -i id_ed25519 -o StrictHostKeyChecking=accept-new root@$ip "hostname" ``` diff --git a/examples/possible_exit_codes.md b/examples/possible_exit_codes.md index 97724853b..24c37404e 100644 --- a/examples/possible_exit_codes.md +++ b/examples/possible_exit_codes.md @@ -15,7 +15,7 @@ upctl server create # Error: required flag(s) "hostname", "zone" not set ``` -Let's create two server and stop one of those to later see other failing exit codes. These command should succceed, and thus return zero exit code. +Let's create two servers and stop one of those to later see other failing exit codes. This example uses `--type hard` when stopping the servers as the OS might not be completely up and running when the server reaches running state. These command should succeed, and thus return zero exit code. ```sh # Create ssh-key into current working directory @@ -24,19 +24,19 @@ ssh-keygen -t ed25519 -q -f "./id_ed25519" -N "" upctl server create --hostname ${prefix}vm-1 --zone pl-waw1 --ssh-keys ./id_ed25519.pub --wait upctl server create --hostname ${prefix}vm-2 --zone pl-waw1 --ssh-keys ./id_ed25519.pub --wait -upctl server stop ${prefix}vm-1 --wait +upctl server stop --type hard ${prefix}vm-1 --wait ``` Now let's try to stop both both of the created servers. Exit code will be one, as `${prefix}vm-1` is already stopped and thus cannot be stopped again. `${prefix}vm-2`, though, will be stopped as it was online. Thus one of the two operations failed. ```sh exit_code=1 -upctl server stop ${prefix}vm-1 ${prefix}vm-2 --wait +upctl server stop --type hard ${prefix}vm-1 ${prefix}vm-2 --wait ``` If we now try to run above command again, exit code will be two as both of the servers are already stopped. Thus both stop operations failed. ```sh exit_code=2 -upctl server stop ${prefix}vm-1 ${prefix}vm-2 --wait +upctl server stop --type hard ${prefix}vm-1 ${prefix}vm-2 --wait ``` Finally, we can cleanup the created resources. diff --git a/go.mod b/go.mod index 3fc900659..180bf7e9a 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.21 require ( github.com/UpCloudLtd/progress v1.0.2 - github.com/UpCloudLtd/upcloud-go-api/v8 v8.4.0 + github.com/UpCloudLtd/upcloud-go-api/v8 v8.6.2 github.com/adrg/xdg v0.3.2 github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d github.com/gemalto/flume v0.12.0 diff --git a/go.sum b/go.sum index 51c22e750..bdad4d023 100644 --- a/go.sum +++ b/go.sum @@ -17,8 +17,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/UpCloudLtd/progress v1.0.2 h1:CTr1bBuFuXop9TEhR1PakbUMPTlUVL7Bgae9JgqXwPg= github.com/UpCloudLtd/progress v1.0.2/go.mod h1:iGxOnb9HvHW0yrLGUjHr0lxHhn7TehgWwh7a8NqK6iQ= -github.com/UpCloudLtd/upcloud-go-api/v8 v8.4.0 h1:xtc+lscOHhrA4d2N8ifjAOu0zL/d8OXhnFI1w9BIbPg= -github.com/UpCloudLtd/upcloud-go-api/v8 v8.4.0/go.mod h1:/BL9bYxio0GCdotzBvZjkpm1fSDtD0+0z6PtNMew9HU= +github.com/UpCloudLtd/upcloud-go-api/v8 v8.6.2 h1:jmHb2PBA4fbDQRR4WrpD94ncSqQ8EAnzJ7PZS0AmALk= +github.com/UpCloudLtd/upcloud-go-api/v8 v8.6.2/go.mod h1:/BL9bYxio0GCdotzBvZjkpm1fSDtD0+0z6PtNMew9HU= github.com/adrg/xdg v0.3.2 h1:GUSGQ5pHdev83AYhDSS1A/CX+0JIsxbiWtow2DSA+RU= github.com/adrg/xdg v0.3.2/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= diff --git a/internal/commands/server/create.go b/internal/commands/server/create.go index 92401a3fd..7332a6dff 100644 --- a/internal/commands/server/create.go +++ b/internal/commands/server/create.go @@ -27,14 +27,14 @@ func CreateCommand() commands.Command { return &createCommand{ BaseCommand: commands.New( "create", - "Create a server", - "upctl server create --title myapp --zone fi-hel1 --hostname myapp --password-delivery email", - "upctl server create --wait --title myapp --zone fi-hel1 --hostname myapp --password-delivery email", - "upctl server create --title \"My Server\" --zone fi-hel1 --hostname myapp --password-delivery email", - "upctl server create --zone fi-hel1 --hostname myapp --password-delivery email --plan 2xCPU-4GB", - "upctl server create --zone fi-hel1 --hostname myapp --password-delivery email --plan custom --cores 2 --memory 4096", + "Create a new server", + "upctl server create --title myapp --zone fi-hel1 --hostname myapp --ssh-keys ~/.ssh/id_*.pub", + "upctl server create --wait --title myapp --zone fi-hel1 --hostname myapp --ssh-keys ~/.ssh/id_*.pub", + "upctl server create --title \"My Server\" --zone fi-hel1 --hostname myapp --ssh-keys ~/.ssh/id_*.pub", + "upctl server create --zone fi-hel1 --hostname myapp --ssh-keys ~/.ssh/id_*.pub --plan 2xCPU-4GB", + "upctl server create --zone fi-hel1 --hostname myapp --ssh-keys ~/.ssh/id_*.pub --plan custom --cores 2 --memory 4096", "upctl server create --zone fi-hel1 --hostname myapp --password-delivery email --os \"Debian GNU/Linux 10 (Buster)\" --server-group a4643646-8342-4324-4134-364138712378", - "upctl server create --zone fi-hel1 --hostname myapp --ssh-keys /path/to/publickey --network type=private,network=037a530b-533e-4cef-b6ad-6af8094bb2bc,ip-address=10.0.0.1", + "upctl server create --zone fi-hel1 --hostname myapp --ssh-keys ~/.ssh/id_*.pub --network type=private,network=037a530b-533e-4cef-b6ad-6af8094bb2bc,ip-address=10.0.0.1", ), } } @@ -48,7 +48,7 @@ var defaultCreateParams = &createParams{ }, firewall: false, metadata: false, - os: "Ubuntu Server 20.04 LTS (Focal Fossa)", + os: "Ubuntu Server 24.04 LTS (Noble Numbat)", osStorageSize: 0, sshKeys: nil, username: "", @@ -96,6 +96,11 @@ func (s *createParams) processParams(exec commands.Executor) error { } } + // Enable metadata service for cloud-init templates. Leave empty for other templates to use value defined by user. + if osStorage.TemplateType == upcloud.StorageTemplateTypeCloudInit { + s.CreateServerRequest.Metadata = upcloud.True + } + s.StorageDevices = append(s.StorageDevices, request.CreateServerStorageDevice{ Action: "clone", Address: "virtio", @@ -252,6 +257,10 @@ type createCommand struct { // InitCommand implements Command.InitCommand func (s *createCommand) InitCommand() { + s.Cobra().Long = commands.WrapLongDescription(`Create a new server + + Note that the default template, Ubuntu Server 24.04 LTS (Noble Numbat), only supports SSH key based authentication. Use ` + "`" + `--ssh-keys` + "`" + ` option to provide the keys when creating a server with the default template. The examples below use public key from the ` + "`" + `~/.ssh` + "`" + ` directory. If you want to use different authentication method, use ` + "`" + `--os` + "`" + ` parameter to specify a different template.`) + fs := &pflag.FlagSet{} s.params = createParams{CreateServerRequest: request.CreateServerRequest{}} def := defaultCreateParams @@ -260,7 +269,7 @@ func (s *createCommand) InitCommand() { fs.IntVar(&s.params.CoreNumber, "cores", def.CoreNumber, "Number of cores. Only allowed if `plan` option is set to \"custom\".") config.AddToggleFlag(fs, &s.createPassword, "create-password", def.createPassword, "Create an admin password.") config.AddEnableOrDisableFlag(fs, &s.firewall, def.firewall, "firewall", "firewall") - config.AddEnableOrDisableFlag(fs, &s.metadata, def.metadata, "metadata", "metadata service") + config.AddEnableOrDisableFlag(fs, &s.metadata, def.metadata, "metadata", "metadata service. The metadata service will be enabled by default, if the selected OS template uses cloud-init and thus requires metadata service") config.AddEnableOrDisableFlag(fs, &s.remoteAccess, def.remoteAccess, "remote-access", "remote access") fs.IntVar(&s.params.Host, "host", def.Host, hostDescription) fs.StringVar(&s.params.Hostname, "hostname", def.Hostname, "Server hostname.") @@ -335,7 +344,9 @@ func (s *createCommand) ExecuteWithoutArguments(exec commands.Executor) (output. if s.firewall.Value() { req.Firewall = "on" } - req.Metadata = s.metadata.AsUpcloudBoolean() + if req.Metadata.Empty() { + req.Metadata = s.metadata.AsUpcloudBoolean() + } req.RemoteAccessEnabled = s.remoteAccess.AsUpcloudBoolean() if s.createPassword.Value() { req.LoginUser.CreatePassword = "yes" diff --git a/internal/commands/server/create_test.go b/internal/commands/server/create_test.go index d88d19c62..532005616 100644 --- a/internal/commands/server/create_test.go +++ b/internal/commands/server/create_test.go @@ -20,7 +20,6 @@ var ( Title1 = "mock-storage-title1" Title2 = "mock-storage-title2" UUID1 = "0127dfd6-3884-4079-a948-3a8881df1a7a" - UUID2 = "012bde1d-f0e7-4bb2-9f4a-74e1f2b49c07" UUID3 = "012c61a6-b8f0-48c2-a63a-b4bf7d26a655" PrivateNetworkUUID = "03b5b0a0-ad4c-4817-9632-dafdb3ace5d9" MockPrivateIPv4 = "10.0.0.1" @@ -49,15 +48,24 @@ func TestCreateServer(t *testing.T) { Tier: "maxiops", } - StorageDef := upcloud.Storage{ - UUID: UUID2, - Title: "Ubuntu Server 20.04 LTS (Focal Fossa)", - Access: "private", - State: "online", - Type: "normal", - Zone: "fi-hel1", - Size: 40, - Tier: "maxiops", + StorageUbuntu2004 := upcloud.Storage{ + UUID: "01000000-0000-4000-8000-000030200200", + Title: "Ubuntu Server 20.04 LTS (Focal Fossa)", + Access: "public", + State: "online", + Type: "template", + Size: 4, + TemplateType: "native", + } + + StorageUbuntu2404 := upcloud.Storage{ + UUID: "01000000-0000-4000-8000-000030240200", + Title: "Ubuntu Server 24.04 LTS (Noble Numbat)", + Access: "public", + State: "online", + Type: "template", + Size: 4, + TemplateType: "cloud-init", } Storage3 := upcloud.Storage{ @@ -73,7 +81,8 @@ func TestCreateServer(t *testing.T) { storages := &upcloud.Storages{ Storages: []upcloud.Storage{ Storage1, - StorageDef, + StorageUbuntu2004, + StorageUbuntu2404, Storage3, }, } @@ -88,6 +97,7 @@ func TestCreateServer(t *testing.T) { serverDetailsStarted := serverDetailsMaint serverDetailsStarted.State = upcloud.ServerStateStarted + sshKey := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHicO0RGJyOSGeMtrmXK1upkkrL5yOrRdjNFl0FLwV00 Example public key" for _, test := range []struct { name string @@ -101,7 +111,7 @@ func TestCreateServer(t *testing.T) { "--hostname", "example.com", "--title", "test-server", "--zone", "uk-lon1", - "--password-delivery", "email", + "--ssh-keys", sshKey, }, createServerReq: request.CreateServerRequest{ VideoModel: "vga", @@ -110,12 +120,47 @@ func TestCreateServer(t *testing.T) { Hostname: "example.com", Title: "test-server", Zone: "uk-lon1", - PasswordDelivery: "email", - LoginUser: &request.LoginUser{CreatePassword: "yes"}, + Metadata: upcloud.True, + PasswordDelivery: "none", + LoginUser: &request.LoginUser{ + CreatePassword: "no", + SSHKeys: []string{sshKey}, + }, + StorageDevices: request.CreateServerStorageDeviceSlice{request.CreateServerStorageDevice{ + Action: "clone", + Address: "virtio", + Storage: StorageUbuntu2404.UUID, + Title: "example.com-OS", + Size: 50, + Type: upcloud.StorageTypeDisk, + }}, + }, + }, + { + name: "use native template", + args: []string{ + "--hostname", "example.com", + "--title", "test-server", + "--zone", "uk-lon1", + "--os", "Ubuntu Server 20.04 LTS (Focal Fossa)", + "--ssh-keys", sshKey, + }, + createServerReq: request.CreateServerRequest{ + VideoModel: "vga", + TimeZone: "UTC", + Plan: "1xCPU-2GB", + Hostname: "example.com", + Title: "test-server", + Zone: "uk-lon1", + PasswordDelivery: "none", + LoginUser: &request.LoginUser{ + CreatePassword: "no", + SSHKeys: []string{sshKey}, + }, StorageDevices: request.CreateServerStorageDeviceSlice{request.CreateServerStorageDevice{ Action: "clone", Address: "virtio", - Storage: StorageDef.UUID, + Storage: StorageUbuntu2004.UUID, Title: "example.com-OS", Size: 50, Type: upcloud.StorageTypeDisk, @@ -202,7 +247,7 @@ func TestCreateServer(t *testing.T) { StorageDevices: request.CreateServerStorageDeviceSlice{request.CreateServerStorageDevice{ Action: "clone", Address: "virtio", - Storage: StorageDef.UUID, + Storage: StorageUbuntu2404.UUID, Title: "example.com-OS", Size: 10, Type: upcloud.StorageTypeDisk, @@ -231,12 +276,13 @@ func TestCreateServer(t *testing.T) { Hostname: "example.com", Title: "test-server", Zone: "uk-lon1", + Metadata: upcloud.True, PasswordDelivery: "email", LoginUser: &request.LoginUser{CreatePassword: "yes"}, StorageDevices: request.CreateServerStorageDeviceSlice{request.CreateServerStorageDevice{ Action: "clone", Address: "virtio", - Storage: StorageDef.UUID, + Storage: StorageUbuntu2404.UUID, Title: "example.com-OS", Size: 50, Type: upcloud.StorageTypeDisk, @@ -275,6 +321,7 @@ func TestCreateServer(t *testing.T) { Hostname: "example.com", Title: "test-server", Zone: "uk-lon1", + Metadata: upcloud.True, PasswordDelivery: "email", LoginUser: &request.LoginUser{CreatePassword: "yes"}, StorageDevices: request.CreateServerStorageDeviceSlice{ @@ -282,7 +329,7 @@ func TestCreateServer(t *testing.T) { Action: "clone", Address: "virtio", Encrypted: upcloud.FromBool(true), - Storage: StorageDef.UUID, + Storage: StorageUbuntu2404.UUID, Title: "example.com-OS", Size: 50, Type: upcloud.StorageTypeDisk, @@ -333,12 +380,13 @@ func TestCreateServer(t *testing.T) { Hostname: "example.com", Title: "test-server", Zone: "uk-lon1", + Metadata: upcloud.True, PasswordDelivery: "email", LoginUser: &request.LoginUser{CreatePassword: "yes"}, StorageDevices: request.CreateServerStorageDeviceSlice{request.CreateServerStorageDevice{ Action: "clone", Address: "virtio", - Storage: StorageDef.UUID, + Storage: StorageUbuntu2404.UUID, Title: "example.com-OS", Size: 50, Type: upcloud.StorageTypeDisk,