Skip to content

Commit f94d642

Browse files
committed
Support provisoning of Windows Server 2022 in ostests
This is an initial step to be able to test Windows clusters. Signed-off-by: Tom Wieczorek <[email protected]>
1 parent 883c977 commit f94d642

File tree

8 files changed

+205
-51
lines changed

8 files changed

+205
-51
lines changed

hack/ostests/README.md

+6
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ tofu apply
9191
* `ubuntu_2004`: Ubuntu 20.04 LTS
9292
* `ubuntu_2204`: Ubuntu 22.04 LTS
9393
* `ubuntu_2304`: Ubuntu 23.04
94+
* `windows_server_2022`: Windows Server 2022 (runs the control plane on Alpine 3.20)
9495

9596
### `arch`: Node architecture
9697

@@ -125,6 +126,11 @@ tofu output -json | jq -r '
125126

126127
This may be a fixed version number, "stable" or "latest".
127128

129+
### `k0s_executable_path`: The k0s version to deploy
130+
131+
Path to the k0s executable to use, or null if it should be downloaded. Note that
132+
for Windows, the `.exe` suffix is appended automatically.
133+
128134
### `k0s_network_provider`: Network provider
129135

130136
* `kuberouter`

hack/ostests/modules/infra/main.tf

+45-20
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
resource "tls_private_key" "ssh" {
2-
algorithm = "ED25519"
2+
# For Windows instances, AWS enforces RSA, and disallows ed25519 ¯\_(ツ)_/¯
3+
algorithm = "RSA"
4+
rsa_bits = 4096
35
}
46

57
resource "aws_key_pair" "ssh" {
@@ -8,31 +10,52 @@ resource "aws_key_pair" "ssh" {
810
}
911

1012
locals {
11-
default_node_config = {
13+
default_node_config = merge ({
14+
os_type = "linux"
15+
volume = { size = 20 }
16+
}, {
1217
x86_64 = { instance_type = "t3a.small" }
1318
arm64 = { instance_type = "t4g.small" }
14-
}[var.os.arch]
19+
}[var.os.arch])
1520

16-
node_roles = {
21+
node_role_templates = {
1722
controller = {
18-
count = var.controller_num_nodes
19-
is_controller = true, is_worker = false,
20-
node_config = merge(local.default_node_config, var.os.node_configs.default, var.os.node_configs.controller)
23+
data = {
24+
count = var.controller_num_nodes
25+
is_controller = true, is_worker = false,
26+
}
27+
sources = [for s in [var.os.node_configs.controller, var.os.node_configs.default]: s if s != null]
2128
}
2229

2330
"controller+worker" = {
24-
count = var.controller_worker_num_nodes
25-
is_controller = true, is_worker = true,
26-
node_config = merge(local.default_node_config, var.os.node_configs.default, var.os.node_configs.worker, var.os.node_configs.controller_worker)
31+
data = {
32+
count = var.controller_worker_num_nodes
33+
is_controller = true, is_worker = true,
34+
}
35+
sources = [for s in [var.os.node_configs.controller_worker, var.os.node_configs.worker, var.os.node_configs.default]: s if s != null]
2736
}
2837

2938
worker = {
30-
count = var.worker_num_nodes
31-
is_controller = false, is_worker = true,
32-
node_config = merge(local.default_node_config, var.os.node_configs.default, var.os.node_configs.worker)
39+
data = {
40+
count = var.worker_num_nodes
41+
is_controller = false, is_worker = true,
42+
}
43+
sources = [for s in [var.os.node_configs.worker, var.os.node_configs.default]: s if s != null]
3344
}
3445
}
3546

47+
node_roles = { for role, tmpl in local.node_role_templates: role => merge(tmpl.data, {
48+
node_config = {
49+
ami_id = coalesce(tmpl.sources.*.ami_id...)
50+
instance_type = coalesce(concat(tmpl.sources, [local.default_node_config]).*.instance_type...)
51+
os_type = coalesce(concat(tmpl.sources, [local.default_node_config]).*.os_type...)
52+
volume = coalesce(concat(tmpl.sources, [local.default_node_config]).*.volume...)
53+
user_data = try(coalesce(tmpl.sources.*.user_data...), null)
54+
ready_script = try(coalesce(tmpl.sources.*.ready_script...), null)
55+
connection = coalesce(tmpl.sources.*.connection...)
56+
}
57+
})}
58+
3659
nodes = merge([for role, params in local.node_roles : {
3760
for idx in range(params.count) : "${role}-${idx + 1}" => {
3861
role = role
@@ -68,19 +91,20 @@ resource "aws_instance" "nodes" {
6891

6992
root_block_device {
7093
volume_type = "gp2"
71-
volume_size = 20
94+
volume_size = each.value.node_config.volume.size
7295
}
7396
}
7497

7598
resource "terraform_data" "ready_scripts" {
7699
for_each = { for name, params in local.nodes : name => params if params.node_config.ready_script != null }
77100

78101
connection {
79-
type = each.value.node_config.connection.type
80-
user = each.value.node_config.connection.username
81-
private_key = tls_private_key.ssh.private_key_pem
82-
host = aws_instance.nodes[each.key].public_ip
83-
agent = false
102+
type = each.value.node_config.connection.type
103+
user = each.value.node_config.connection.username
104+
target_platform = each.value.node_config.os_type == "windows" ? "windows" : "unix"
105+
private_key = tls_private_key.ssh.private_key_pem
106+
host = aws_instance.nodes[each.key].public_ip
107+
agent = false
84108
}
85109

86110
provisioner "remote-exec" {
@@ -93,10 +117,11 @@ resource "terraform_data" "provisioned_nodes" {
93117

94118
input = [for node in aws_instance.nodes : {
95119
name = node.tags.Name,
96-
ipv4 = node.public_ip,
120+
os_type = local.nodes[node.tags["ostests.k0sproject.io/node-name"]].node_config.os_type,
97121
role = node.tags["k0sctl.k0sproject.io/host-role"]
98122
is_controller = local.nodes[node.tags["ostests.k0sproject.io/node-name"]].is_controller
99123
is_worker = local.nodes[node.tags["ostests.k0sproject.io/node-name"]].is_worker
124+
ipv4 = node.public_ip,
100125
connection = local.nodes[node.tags["ostests.k0sproject.io/node-name"]].node_config.connection
101126
}]
102127
}

hack/ostests/modules/infra/variables.tf

+61-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@ variable "os" {
1313
arch = string
1414
node_configs = object({
1515
default = object({
16-
ami_id = string
16+
ami_id = string
17+
instance_type = optional(string)
18+
os_type = optional(string)
19+
20+
volume = optional(object({
21+
size = number
22+
}))
1723

1824
user_data = optional(string)
1925
ready_script = optional(string)
@@ -23,9 +29,60 @@ variable "os" {
2329
username = string
2430
})
2531
})
26-
controller = optional(map(any))
27-
controller_worker = optional(map(any))
28-
worker = optional(map(any))
32+
33+
controller = optional(object({
34+
ami_id = optional(string)
35+
instance_type = optional(string)
36+
os_type = optional(string)
37+
38+
volume = optional(object({
39+
size = number
40+
}))
41+
42+
user_data = optional(string)
43+
ready_script = optional(string)
44+
45+
connection = optional(object({
46+
type = string
47+
username = string
48+
}))
49+
}))
50+
51+
controller_worker = optional(object({
52+
ami_id = optional(string)
53+
instance_type = optional(string)
54+
os_type = optional(string)
55+
56+
volume = optional(object({
57+
size = number
58+
}))
59+
60+
user_data = optional(string)
61+
ready_script = optional(string)
62+
63+
connection = optional(object({
64+
type = string
65+
username = string
66+
}))
67+
}))
68+
69+
worker = optional(object({
70+
ami_id = optional(string)
71+
instance_type = optional(string)
72+
os_type = optional(string)
73+
74+
volume = optional(object({
75+
size = number
76+
}))
77+
78+
user_data = optional(string)
79+
ready_script = optional(string)
80+
81+
connection = optional(object({
82+
type = string
83+
username = string
84+
}))
85+
}))
2986
})
3087
})
3188

hack/ostests/modules/k0sctl/config.tf

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ locals {
3939
}),
4040

4141
var.k0s_executable_path == null ? {} : {
42-
k0sBinaryPath = var.k0s_executable_path
42+
k0sBinaryPath = format("%s%s", var.k0s_executable_path, host.os_type == "windows" ? ".exe" : "")
4343
},
4444
)]
4545
}

hack/ostests/modules/k0sctl/variables.tf

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ variable "hosts" {
22
type = list(
33
object({
44
name = string,
5+
os_type = string,
56
role = string,
67
is_controller = bool,
78
is_worker = bool,
8-
ipv4 = optional(string),
9+
ipv4 = string,
910
connection = object({
1011
type = string
1112
username = string

hack/ostests/modules/os/main.tf

+24-23
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,29 @@ locals {
22
# Boilerplate to make OpenTofu a little bit dynamic
33

44
os = {
5-
al2023 = local.os_al2023
6-
alpine_3_17 = local.os_alpine_3_17
7-
alpine_3_20 = local.os_alpine_3_20
8-
centos_7 = local.os_centos_7
9-
centos_8 = local.os_centos_8
10-
centos_9 = local.os_centos_9
11-
debian_10 = local.os_debian_10
12-
debian_11 = local.os_debian_11
13-
debian_12 = local.os_debian_12
14-
fcos_38 = local.os_fcos_38
15-
fedora_38 = local.os_fedora_38
16-
flatcar = local.os_flatcar
17-
oracle_7_9 = local.os_oracle_7_9
18-
oracle_8_7 = local.os_oracle_8_7
19-
oracle_9_1 = local.os_oracle_9_1
20-
rhel_7 = local.os_rhel_7
21-
rhel_8 = local.os_rhel_8
22-
rhel_9 = local.os_rhel_9
23-
rocky_8 = local.os_rocky_8
24-
rocky_9 = local.os_rocky_9
25-
ubuntu_2004 = local.os_ubuntu_2004
26-
ubuntu_2204 = local.os_ubuntu_2204
27-
ubuntu_2304 = local.os_ubuntu_2304
5+
al2023 = local.os_al2023
6+
alpine_3_17 = local.os_alpine_3_17
7+
alpine_3_20 = local.os_alpine_3_20
8+
centos_7 = local.os_centos_7
9+
centos_8 = local.os_centos_8
10+
centos_9 = local.os_centos_9
11+
debian_10 = local.os_debian_10
12+
debian_11 = local.os_debian_11
13+
debian_12 = local.os_debian_12
14+
fcos_38 = local.os_fcos_38
15+
fedora_38 = local.os_fedora_38
16+
flatcar = local.os_flatcar
17+
oracle_7_9 = local.os_oracle_7_9
18+
oracle_8_7 = local.os_oracle_8_7
19+
oracle_9_1 = local.os_oracle_9_1
20+
rhel_7 = local.os_rhel_7
21+
rhel_8 = local.os_rhel_8
22+
rhel_9 = local.os_rhel_9
23+
rocky_8 = local.os_rocky_8
24+
rocky_9 = local.os_rocky_9
25+
ubuntu_2004 = local.os_ubuntu_2004
26+
ubuntu_2204 = local.os_ubuntu_2204
27+
ubuntu_2304 = local.os_ubuntu_2304
28+
windows_server_2022 = local.os_windows_server_2022
2829
}
2930
}

hack/ostests/modules/os/os_alpine_3_20.tf

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# https://www.alpinelinux.org/cloud/
22

33
data "aws_ami" "alpine_3_20" {
4-
count = var.os == "alpine_3_20" ? 1 : 0
4+
count = (var.os == "alpine_3_20" || startswith(var.os, "windows_")) ? 1 : 0
55

66
owners = ["538276064493"]
77
name_regex = "^alpine-3\\.20\\.\\d+-(aarch64|x86_64)-uefi-tiny($|-.*)"
@@ -29,7 +29,7 @@ data "aws_ami" "alpine_3_20" {
2929
}
3030

3131
locals {
32-
os_alpine_3_20 = var.os != "alpine_3_20" ? {} : {
32+
os_alpine_3_20 = (var.os != "alpine_3_20" && !startswith(var.os, "windows_")) ? {} : {
3333
node_configs = {
3434
default = {
3535
ami_id = one(data.aws_ami.alpine_3_20.*.id)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
data "aws_ami" "windows_server_2022" {
2+
count = var.os == "windows_server_2022" ? 1 : 0
3+
4+
owners = ["801119661308"] # amazon
5+
name_regex = "Windows_Server-2022-English-Full-Base-\\d+\\.\\d+\\.\\d+"
6+
most_recent = true
7+
8+
filter {
9+
name = "name"
10+
values = ["Windows_Server-2022-English-Full-Base-*"]
11+
}
12+
13+
filter {
14+
name = "architecture"
15+
values = ["x86_64"]
16+
}
17+
18+
filter {
19+
name = "root-device-type"
20+
values = ["ebs"]
21+
}
22+
23+
filter {
24+
name = "virtualization-type"
25+
values = ["hvm"]
26+
}
27+
28+
lifecycle {
29+
precondition {
30+
condition = var.arch == "x86_64"
31+
error_message = "Unsupported architecture for Windows Server 2022."
32+
}
33+
}
34+
}
35+
36+
locals {
37+
os_windows_server_2022 = var.os != "windows_server_2022" ? {} : {
38+
node_configs = {
39+
default = local.os_alpine_3_20.node_configs.default
40+
controller = local.os_alpine_3_20.node_configs.controller
41+
42+
worker = {
43+
ami_id = one(data.aws_ami.windows_server_2022.*.id)
44+
instance_type = "t3a.medium"
45+
os_type = "windows"
46+
volume = { size = 50 }
47+
48+
# https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html#user-data-yaml-scripts
49+
# https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2launch-v2-task-definitions.html#ec2launch-v2-enableopenssh
50+
# https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2launch-v2.html#ec2launch-v2-directory
51+
# C:\ProgramData\Amazon\EC2Launch\log\agent.log
52+
user_data = jsonencode({ version = 1.1, tasks = [{ task = "enableOpenSsh" }]})
53+
54+
# Override the default Alpine ready script. Also checks SSH connectivity.
55+
ready_script = "whoami"
56+
57+
connection = {
58+
type = "ssh"
59+
username = "Administrator"
60+
}
61+
}
62+
}
63+
}
64+
}

0 commit comments

Comments
 (0)