hetzner-k3s 0.4.9 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +121 -0
- data/Dockerfile +5 -2
- data/Gemfile +5 -3
- data/Gemfile.lock +24 -3
- data/README.md +7 -85
- data/Rakefile +5 -3
- data/bin/build.sh +3 -3
- data/bin/{console → console.sh} +3 -3
- data/bin/{setup → setup.sh} +0 -0
- data/hetzner-k3s.gemspec +25 -21
- data/lib/hetzner/infra/client.rb +17 -15
- data/lib/hetzner/infra/firewall.rb +92 -90
- data/lib/hetzner/infra/load_balancer.rb +62 -59
- data/lib/hetzner/infra/network.rb +31 -30
- data/lib/hetzner/infra/placement_group.rb +25 -21
- data/lib/hetzner/infra/server.rb +37 -31
- data/lib/hetzner/infra/ssh_key.rb +34 -35
- data/lib/hetzner/infra.rb +5 -1
- data/lib/hetzner/k3s/cli.rb +279 -262
- data/lib/hetzner/k3s/cluster.rb +448 -397
- data/lib/hetzner/k3s/version.rb +3 -1
- data/lib/hetzner/utils.rb +42 -38
- data/lib/hetzner.rb +2 -0
- metadata +26 -10
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Hetzner
|
|
2
4
|
class Firewall
|
|
3
5
|
def initialize(hetzner_client:, cluster_name:)
|
|
@@ -5,38 +7,39 @@ module Hetzner
|
|
|
5
7
|
@cluster_name = cluster_name
|
|
6
8
|
end
|
|
7
9
|
|
|
8
|
-
def create(
|
|
9
|
-
@
|
|
10
|
+
def create(high_availability:, networks:)
|
|
11
|
+
@high_availability = high_availability
|
|
10
12
|
@networks = networks
|
|
11
13
|
puts
|
|
12
14
|
|
|
13
|
-
if firewall = find_firewall
|
|
14
|
-
puts
|
|
15
|
+
if (firewall = find_firewall)
|
|
16
|
+
puts 'Firewall already exists, skipping.'
|
|
15
17
|
puts
|
|
16
|
-
return firewall[
|
|
18
|
+
return firewall['id']
|
|
17
19
|
end
|
|
18
20
|
|
|
19
|
-
puts
|
|
21
|
+
puts 'Creating firewall...'
|
|
20
22
|
|
|
21
|
-
response = hetzner_client.post(
|
|
22
|
-
puts
|
|
23
|
+
response = hetzner_client.post('/firewalls', create_firewall_config).body
|
|
24
|
+
puts '...firewall created.'
|
|
23
25
|
puts
|
|
24
26
|
|
|
25
|
-
JSON.parse(response)[
|
|
27
|
+
JSON.parse(response)['firewall']['id']
|
|
26
28
|
end
|
|
27
29
|
|
|
28
30
|
def delete(servers)
|
|
29
|
-
if firewall = find_firewall
|
|
30
|
-
puts
|
|
31
|
+
if (firewall = find_firewall)
|
|
32
|
+
puts 'Deleting firewall...'
|
|
31
33
|
|
|
32
34
|
servers.each do |server|
|
|
33
|
-
hetzner_client.post("/firewalls/#{firewall[
|
|
35
|
+
hetzner_client.post("/firewalls/#{firewall['id']}/actions/remove_from_resources",
|
|
36
|
+
remove_targets_config(server['id']))
|
|
34
37
|
end
|
|
35
38
|
|
|
36
|
-
hetzner_client.delete(
|
|
37
|
-
puts
|
|
39
|
+
hetzner_client.delete('/firewalls', firewall['id'])
|
|
40
|
+
puts '...firewall deleted.'
|
|
38
41
|
else
|
|
39
|
-
puts
|
|
42
|
+
puts 'Firewall no longer exists, skipping.'
|
|
40
43
|
end
|
|
41
44
|
|
|
42
45
|
puts
|
|
@@ -44,87 +47,86 @@ module Hetzner
|
|
|
44
47
|
|
|
45
48
|
private
|
|
46
49
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def create_firewall_config
|
|
50
|
-
rules = [
|
|
51
|
-
{
|
|
52
|
-
"description": "Allow port 22 (SSH)",
|
|
53
|
-
"direction": "in",
|
|
54
|
-
"protocol": "tcp",
|
|
55
|
-
"port": "22",
|
|
56
|
-
"source_ips": networks,
|
|
57
|
-
"destination_ips": []
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
"description": "Allow ICMP (ping)",
|
|
61
|
-
"direction": "in",
|
|
62
|
-
"protocol": "icmp",
|
|
63
|
-
"port": nil,
|
|
64
|
-
"source_ips": [
|
|
65
|
-
"0.0.0.0/0",
|
|
66
|
-
"::/0"
|
|
67
|
-
],
|
|
68
|
-
"destination_ips": []
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
"description": "Allow all TCP traffic between nodes on the private network",
|
|
72
|
-
"direction": "in",
|
|
73
|
-
"protocol": "tcp",
|
|
74
|
-
"port": "any",
|
|
75
|
-
"source_ips": [
|
|
76
|
-
"10.0.0.0/16"
|
|
77
|
-
],
|
|
78
|
-
"destination_ips": []
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
"description": "Allow all UDP traffic between nodes on the private network",
|
|
82
|
-
"direction": "in",
|
|
83
|
-
"protocol": "udp",
|
|
84
|
-
"port": "any",
|
|
85
|
-
"source_ips": [
|
|
86
|
-
"10.0.0.0/16"
|
|
87
|
-
],
|
|
88
|
-
"destination_ips": []
|
|
89
|
-
}
|
|
90
|
-
]
|
|
91
|
-
|
|
92
|
-
unless ha
|
|
93
|
-
rules << {
|
|
94
|
-
"description": "Allow port 6443 (Kubernetes API server)",
|
|
95
|
-
"direction": "in",
|
|
96
|
-
"protocol": "tcp",
|
|
97
|
-
"port": "6443",
|
|
98
|
-
"source_ips": [
|
|
99
|
-
"0.0.0.0/0",
|
|
100
|
-
"::/0"
|
|
101
|
-
],
|
|
102
|
-
"destination_ips": []
|
|
103
|
-
}
|
|
104
|
-
end
|
|
50
|
+
attr_reader :hetzner_client, :cluster_name, :firewall, :high_availability, :networks
|
|
105
51
|
|
|
52
|
+
def create_firewall_config
|
|
53
|
+
rules = [
|
|
106
54
|
{
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
55
|
+
description: 'Allow port 22 (SSH)',
|
|
56
|
+
direction: 'in',
|
|
57
|
+
protocol: 'tcp',
|
|
58
|
+
port: '22',
|
|
59
|
+
source_ips: networks,
|
|
60
|
+
destination_ips: []
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
description: 'Allow ICMP (ping)',
|
|
64
|
+
direction: 'in',
|
|
65
|
+
protocol: 'icmp',
|
|
66
|
+
port: nil,
|
|
67
|
+
source_ips: [
|
|
68
|
+
'0.0.0.0/0',
|
|
69
|
+
'::/0'
|
|
70
|
+
],
|
|
71
|
+
destination_ips: []
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
description: 'Allow all TCP traffic between nodes on the private network',
|
|
75
|
+
direction: 'in',
|
|
76
|
+
protocol: 'tcp',
|
|
77
|
+
port: 'any',
|
|
78
|
+
source_ips: [
|
|
79
|
+
'10.0.0.0/16'
|
|
80
|
+
],
|
|
81
|
+
destination_ips: []
|
|
82
|
+
},
|
|
113
83
|
{
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
]
|
|
84
|
+
description: 'Allow all UDP traffic between nodes on the private network',
|
|
85
|
+
direction: 'in',
|
|
86
|
+
protocol: 'udp',
|
|
87
|
+
port: 'any',
|
|
88
|
+
source_ips: [
|
|
89
|
+
'10.0.0.0/16'
|
|
90
|
+
],
|
|
91
|
+
destination_ips: []
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
unless high_availability
|
|
96
|
+
rules << {
|
|
97
|
+
description: 'Allow port 6443 (Kubernetes API server)',
|
|
98
|
+
direction: 'in',
|
|
99
|
+
protocol: 'tcp',
|
|
100
|
+
port: '6443',
|
|
101
|
+
source_ips: [
|
|
102
|
+
'0.0.0.0/0',
|
|
103
|
+
'::/0'
|
|
104
|
+
],
|
|
105
|
+
destination_ips: []
|
|
122
106
|
}
|
|
123
107
|
end
|
|
124
108
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
109
|
+
{
|
|
110
|
+
name: cluster_name,
|
|
111
|
+
rules:
|
|
112
|
+
}
|
|
113
|
+
end
|
|
128
114
|
|
|
115
|
+
def remove_targets_config(server_id)
|
|
116
|
+
{
|
|
117
|
+
remove_from: [
|
|
118
|
+
{
|
|
119
|
+
server: {
|
|
120
|
+
id: server_id
|
|
121
|
+
},
|
|
122
|
+
type: 'server'
|
|
123
|
+
}
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def find_firewall
|
|
129
|
+
hetzner_client.get('/firewalls')['firewalls'].detect { |firewall| firewall['name'] == cluster_name }
|
|
130
|
+
end
|
|
129
131
|
end
|
|
130
132
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Hetzner
|
|
2
4
|
class LoadBalancer
|
|
3
5
|
def initialize(hetzner_client:, cluster_name:)
|
|
@@ -11,31 +13,31 @@ module Hetzner
|
|
|
11
13
|
|
|
12
14
|
puts
|
|
13
15
|
|
|
14
|
-
if load_balancer = find_load_balancer
|
|
15
|
-
puts
|
|
16
|
+
if (load_balancer = find_load_balancer)
|
|
17
|
+
puts 'API load balancer already exists, skipping.'
|
|
16
18
|
puts
|
|
17
|
-
return load_balancer[
|
|
19
|
+
return load_balancer['id']
|
|
18
20
|
end
|
|
19
21
|
|
|
20
|
-
puts
|
|
22
|
+
puts 'Creating API load_balancer...'
|
|
21
23
|
|
|
22
|
-
response = hetzner_client.post(
|
|
23
|
-
puts
|
|
24
|
+
response = hetzner_client.post('/load_balancers', create_load_balancer_config).body
|
|
25
|
+
puts '...API load balancer created.'
|
|
24
26
|
puts
|
|
25
27
|
|
|
26
|
-
JSON.parse(response)[
|
|
28
|
+
JSON.parse(response)['load_balancer']['id']
|
|
27
29
|
end
|
|
28
30
|
|
|
29
|
-
def delete(
|
|
30
|
-
if load_balancer = find_load_balancer
|
|
31
|
-
puts
|
|
31
|
+
def delete(high_availability:)
|
|
32
|
+
if (load_balancer = find_load_balancer)
|
|
33
|
+
puts 'Deleting API load balancer...' unless high_availability
|
|
32
34
|
|
|
33
|
-
hetzner_client.post("/load_balancers/#{load_balancer[
|
|
35
|
+
hetzner_client.post("/load_balancers/#{load_balancer['id']}/actions/remove_target", remove_targets_config)
|
|
34
36
|
|
|
35
|
-
hetzner_client.delete(
|
|
36
|
-
puts
|
|
37
|
-
elsif
|
|
38
|
-
puts
|
|
37
|
+
hetzner_client.delete('/load_balancers', load_balancer['id'])
|
|
38
|
+
puts '...API load balancer deleted.' unless high_availability
|
|
39
|
+
elsif high_availability
|
|
40
|
+
puts 'API load balancer no longer exists, skipping.'
|
|
39
41
|
end
|
|
40
42
|
|
|
41
43
|
puts
|
|
@@ -43,54 +45,55 @@ module Hetzner
|
|
|
43
45
|
|
|
44
46
|
private
|
|
45
47
|
|
|
46
|
-
|
|
48
|
+
attr_reader :hetzner_client, :cluster_name, :load_balancer, :location, :network_id
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
def load_balancer_name
|
|
51
|
+
"#{cluster_name}-api"
|
|
52
|
+
end
|
|
51
53
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
54
|
+
def create_load_balancer_config
|
|
55
|
+
{
|
|
56
|
+
algorithm: {
|
|
57
|
+
type: 'round_robin'
|
|
58
|
+
},
|
|
59
|
+
load_balancer_type: 'lb11',
|
|
60
|
+
location:,
|
|
61
|
+
name: load_balancer_name,
|
|
62
|
+
network: network_id,
|
|
63
|
+
public_interface: true,
|
|
64
|
+
services: [
|
|
65
|
+
{
|
|
66
|
+
destination_port: 6443,
|
|
67
|
+
listen_port: 6443,
|
|
68
|
+
protocol: 'tcp',
|
|
69
|
+
proxyprotocol: false
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
targets: [
|
|
73
|
+
{
|
|
74
|
+
label_selector: {
|
|
75
|
+
selector: "cluster=#{cluster_name},role=master"
|
|
76
|
+
},
|
|
77
|
+
type: 'label_selector',
|
|
78
|
+
use_private_ip: true
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
end
|
|
81
83
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
84
|
+
def remove_targets_config
|
|
85
|
+
{
|
|
86
|
+
label_selector: {
|
|
87
|
+
selector: "cluster=#{cluster_name},role=master"
|
|
88
|
+
},
|
|
89
|
+
type: 'label_selector'
|
|
90
|
+
}
|
|
91
|
+
end
|
|
90
92
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
def find_load_balancer
|
|
94
|
+
hetzner_client.get('/load_balancers')['load_balancers'].detect do |load_balancer|
|
|
95
|
+
load_balancer['name'] == load_balancer_name
|
|
93
96
|
end
|
|
94
|
-
|
|
97
|
+
end
|
|
95
98
|
end
|
|
96
99
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Hetzner
|
|
2
4
|
class Network
|
|
3
5
|
def initialize(hetzner_client:, cluster_name:)
|
|
@@ -9,29 +11,29 @@ module Hetzner
|
|
|
9
11
|
@location = location
|
|
10
12
|
puts
|
|
11
13
|
|
|
12
|
-
if network = find_network
|
|
13
|
-
puts
|
|
14
|
+
if (network = find_network)
|
|
15
|
+
puts 'Private network already exists, skipping.'
|
|
14
16
|
puts
|
|
15
|
-
return network[
|
|
17
|
+
return network['id']
|
|
16
18
|
end
|
|
17
19
|
|
|
18
|
-
puts
|
|
20
|
+
puts 'Creating private network...'
|
|
19
21
|
|
|
20
|
-
response = hetzner_client.post(
|
|
22
|
+
response = hetzner_client.post('/networks', network_config).body
|
|
21
23
|
|
|
22
|
-
puts
|
|
24
|
+
puts '...private network created.'
|
|
23
25
|
puts
|
|
24
26
|
|
|
25
|
-
JSON.parse(response)[
|
|
27
|
+
JSON.parse(response)['network']['id']
|
|
26
28
|
end
|
|
27
29
|
|
|
28
30
|
def delete
|
|
29
|
-
if network = find_network
|
|
30
|
-
puts
|
|
31
|
-
hetzner_client.delete(
|
|
32
|
-
puts
|
|
31
|
+
if (network = find_network)
|
|
32
|
+
puts 'Deleting network...'
|
|
33
|
+
hetzner_client.delete('/networks', network['id'])
|
|
34
|
+
puts '...network deleted.'
|
|
33
35
|
else
|
|
34
|
-
puts
|
|
36
|
+
puts 'Network no longer exists, skipping.'
|
|
35
37
|
end
|
|
36
38
|
|
|
37
39
|
puts
|
|
@@ -39,25 +41,24 @@ module Hetzner
|
|
|
39
41
|
|
|
40
42
|
private
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def network_config
|
|
45
|
-
{
|
|
46
|
-
name: cluster_name,
|
|
47
|
-
ip_range: "10.0.0.0/16",
|
|
48
|
-
subnets: [
|
|
49
|
-
{
|
|
50
|
-
ip_range: "10.0.0.0/16",
|
|
51
|
-
network_zone: (location == "ash" ? "us-east" : "eu-central"),
|
|
52
|
-
type: "cloud"
|
|
53
|
-
}
|
|
54
|
-
]
|
|
55
|
-
}
|
|
56
|
-
end
|
|
44
|
+
attr_reader :hetzner_client, :cluster_name, :location
|
|
57
45
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
46
|
+
def network_config
|
|
47
|
+
{
|
|
48
|
+
name: cluster_name,
|
|
49
|
+
ip_range: '10.0.0.0/16',
|
|
50
|
+
subnets: [
|
|
51
|
+
{
|
|
52
|
+
ip_range: '10.0.0.0/16',
|
|
53
|
+
network_zone: (location == 'ash' ? 'us-east' : 'eu-central'),
|
|
54
|
+
type: 'cloud'
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
end
|
|
61
59
|
|
|
60
|
+
def find_network
|
|
61
|
+
hetzner_client.get('/networks')['networks'].detect { |network| network['name'] == cluster_name }
|
|
62
|
+
end
|
|
62
63
|
end
|
|
63
64
|
end
|
|
@@ -1,36 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Hetzner
|
|
2
4
|
class PlacementGroup
|
|
3
|
-
def initialize(hetzner_client:, cluster_name:)
|
|
5
|
+
def initialize(hetzner_client:, cluster_name:, pool_name: nil)
|
|
4
6
|
@hetzner_client = hetzner_client
|
|
5
7
|
@cluster_name = cluster_name
|
|
8
|
+
@placement_group_name = pool_name ? "#{cluster_name}-#{pool_name}" : cluster_name
|
|
6
9
|
end
|
|
7
10
|
|
|
8
11
|
def create
|
|
9
12
|
puts
|
|
10
13
|
|
|
11
14
|
if (placement_group = find_placement_group)
|
|
12
|
-
puts "Placement group already exists, skipping."
|
|
15
|
+
puts "Placement group #{placement_group_name} already exists, skipping."
|
|
13
16
|
puts
|
|
14
|
-
return placement_group[
|
|
17
|
+
return placement_group['id']
|
|
15
18
|
end
|
|
16
19
|
|
|
17
|
-
puts "Creating placement group..."
|
|
20
|
+
puts "Creating placement group #{placement_group_name}..."
|
|
18
21
|
|
|
19
|
-
response = hetzner_client.post(
|
|
22
|
+
response = hetzner_client.post('/placement_groups', placement_group_config).body
|
|
20
23
|
|
|
21
|
-
puts "...placement group created."
|
|
24
|
+
puts "...placement group #{placement_group_name} created."
|
|
22
25
|
puts
|
|
23
26
|
|
|
24
|
-
JSON.parse(response)[
|
|
27
|
+
JSON.parse(response)['placement_group']['id']
|
|
25
28
|
end
|
|
26
29
|
|
|
27
30
|
def delete
|
|
28
31
|
if (placement_group = find_placement_group)
|
|
29
|
-
puts "Deleting placement group..."
|
|
30
|
-
hetzner_client.delete(
|
|
31
|
-
puts "...placement group deleted."
|
|
32
|
+
puts "Deleting placement group #{placement_group_name}..."
|
|
33
|
+
hetzner_client.delete('/placement_groups', placement_group['id'])
|
|
34
|
+
puts "...placement group #{placement_group_name} deleted."
|
|
32
35
|
else
|
|
33
|
-
puts "Placement group no longer exists, skipping."
|
|
36
|
+
puts "Placement group #{placement_group_name} no longer exists, skipping."
|
|
34
37
|
end
|
|
35
38
|
|
|
36
39
|
puts
|
|
@@ -38,18 +41,19 @@ module Hetzner
|
|
|
38
41
|
|
|
39
42
|
private
|
|
40
43
|
|
|
41
|
-
|
|
44
|
+
attr_reader :hetzner_client, :cluster_name, :placement_group_name
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
def placement_group_config
|
|
47
|
+
{
|
|
48
|
+
name: placement_group_name,
|
|
49
|
+
type: 'spread'
|
|
50
|
+
}
|
|
51
|
+
end
|
|
49
52
|
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
def find_placement_group
|
|
54
|
+
hetzner_client.get('/placement_groups')['placement_groups'].detect do |placement_group|
|
|
55
|
+
placement_group['name'] == placement_group_name
|
|
52
56
|
end
|
|
53
|
-
|
|
57
|
+
end
|
|
54
58
|
end
|
|
55
59
|
end
|
data/lib/hetzner/infra/server.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Hetzner
|
|
2
4
|
class Server
|
|
3
5
|
def initialize(hetzner_client:, cluster_name:)
|
|
@@ -5,12 +7,14 @@ module Hetzner
|
|
|
5
7
|
@cluster_name = cluster_name
|
|
6
8
|
end
|
|
7
9
|
|
|
8
|
-
def create(location:, instance_type:, instance_id:, firewall_id:, network_id:, ssh_key_id:, placement_group_id:, image:)
|
|
10
|
+
def create(location:, instance_type:, instance_id:, firewall_id:, network_id:, ssh_key_id:, placement_group_id:, image:, additional_packages: [])
|
|
11
|
+
@additional_packages = additional_packages
|
|
12
|
+
|
|
9
13
|
puts
|
|
10
14
|
|
|
11
15
|
server_name = "#{cluster_name}-#{instance_type}-#{instance_id}"
|
|
12
16
|
|
|
13
|
-
if server = find_server(server_name)
|
|
17
|
+
if (server = find_server(server_name))
|
|
14
18
|
puts "Server #{server_name} already exists, skipping."
|
|
15
19
|
puts
|
|
16
20
|
return server
|
|
@@ -20,8 +24,8 @@ module Hetzner
|
|
|
20
24
|
|
|
21
25
|
server_config = {
|
|
22
26
|
name: server_name,
|
|
23
|
-
location
|
|
24
|
-
image
|
|
27
|
+
location:,
|
|
28
|
+
image:,
|
|
25
29
|
firewalls: [
|
|
26
30
|
{ firewall: firewall_id }
|
|
27
31
|
],
|
|
@@ -32,18 +36,18 @@ module Hetzner
|
|
|
32
36
|
ssh_keys: [
|
|
33
37
|
ssh_key_id
|
|
34
38
|
],
|
|
35
|
-
user_data
|
|
39
|
+
user_data:,
|
|
36
40
|
labels: {
|
|
37
41
|
cluster: cluster_name,
|
|
38
|
-
role: (server_name =~ /master/ ?
|
|
42
|
+
role: (server_name =~ /master/ ? 'master' : 'worker')
|
|
39
43
|
},
|
|
40
44
|
placement_group: placement_group_id
|
|
41
45
|
}
|
|
42
46
|
|
|
43
|
-
response = hetzner_client.post(
|
|
47
|
+
response = hetzner_client.post('/servers', server_config)
|
|
44
48
|
response_body = response.body
|
|
45
49
|
|
|
46
|
-
server = JSON.parse(response_body)[
|
|
50
|
+
server = JSON.parse(response_body)['server']
|
|
47
51
|
|
|
48
52
|
unless server
|
|
49
53
|
puts "Error creating server #{server_name}. Response details below:"
|
|
@@ -59,9 +63,9 @@ module Hetzner
|
|
|
59
63
|
end
|
|
60
64
|
|
|
61
65
|
def delete(server_name:)
|
|
62
|
-
if server = find_server(server_name)
|
|
66
|
+
if (server = find_server(server_name))
|
|
63
67
|
puts "Deleting server #{server_name}..."
|
|
64
|
-
hetzner_client.delete
|
|
68
|
+
hetzner_client.delete '/servers', server['id']
|
|
65
69
|
puts "...server #{server_name} deleted."
|
|
66
70
|
else
|
|
67
71
|
puts "Server #{server_name} no longer exists, skipping."
|
|
@@ -70,28 +74,30 @@ module Hetzner
|
|
|
70
74
|
|
|
71
75
|
private
|
|
72
76
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def find_server(server_name)
|
|
76
|
-
hetzner_client.get("/servers")["servers"].detect{ |network| network["name"] == server_name }
|
|
77
|
-
end
|
|
77
|
+
attr_reader :hetzner_client, :cluster_name, :additional_packages
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
packages:
|
|
83
|
-
- fail2ban
|
|
84
|
-
runcmd:
|
|
85
|
-
- sed -i 's/[#]*PermitRootLogin yes/PermitRootLogin prohibit-password/g' /etc/ssh/sshd_config
|
|
86
|
-
- sed -i 's/[#]*PasswordAuthentication yes/PasswordAuthentication no/g' /etc/ssh/sshd_config
|
|
87
|
-
- systemctl restart sshd
|
|
88
|
-
- systemctl stop systemd-resolved
|
|
89
|
-
- systemctl disable systemd-resolved
|
|
90
|
-
- rm /etc/resolv.conf
|
|
91
|
-
- echo "nameserver 1.1.1.1" > /etc/resolv.conf
|
|
92
|
-
- echo "nameserver 1.0.0.1" >> /etc/resolv.conf
|
|
93
|
-
EOS
|
|
94
|
-
end
|
|
79
|
+
def find_server(server_name)
|
|
80
|
+
hetzner_client.get('/servers?sort=created:desc')['servers'].detect { |network| network['name'] == server_name }
|
|
81
|
+
end
|
|
95
82
|
|
|
83
|
+
def user_data
|
|
84
|
+
packages = ['fail2ban', 'wireguard']
|
|
85
|
+
packages += additional_packages if additional_packages
|
|
86
|
+
packages = "'#{packages.join("', '")}'"
|
|
87
|
+
|
|
88
|
+
<<~YAML
|
|
89
|
+
#cloud-config
|
|
90
|
+
packages: [#{packages}]
|
|
91
|
+
runcmd:
|
|
92
|
+
- sed -i 's/[#]*PermitRootLogin yes/PermitRootLogin prohibit-password/g' /etc/ssh/sshd_config
|
|
93
|
+
- sed -i 's/[#]*PasswordAuthentication yes/PasswordAuthentication no/g' /etc/ssh/sshd_config
|
|
94
|
+
- systemctl restart sshd
|
|
95
|
+
- systemctl stop systemd-resolved
|
|
96
|
+
- systemctl disable systemd-resolved
|
|
97
|
+
- rm /etc/resolv.conf
|
|
98
|
+
- echo "nameserver 1.1.1.1" > /etc/resolv.conf
|
|
99
|
+
- echo "nameserver 1.0.0.1" >> /etc/resolv.conf
|
|
100
|
+
YAML
|
|
101
|
+
end
|
|
96
102
|
end
|
|
97
103
|
end
|