-
Notifications
You must be signed in to change notification settings - Fork 27
/
destroy-cluster.rb
executable file
·260 lines (213 loc) · 6.5 KB
/
destroy-cluster.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
#!/usr/bin/env ruby
# If any namespaces exist in the cluster which are not
# listed here, the destroy script will abort.
EKS_SYSTEM_NAMESPACES = %w[
cert-manager
default
ingress-controllers
kube-node-lease
kube-public
kube-system
kuberos
logging
monitoring
opa
velero
kuberhealthy
trivy-system
calico-apiserver
calico-system
tigera-operator
] + (0..9).map { |i| "starter-pack-#{i}" }
MAX_CLUSTER_NAME_LENGTH = 12
REQUIRED_ENV_VARS = %w[AWS_PROFILE AUTH0_DOMAIN AUTH0_CLIENT_ID AUTH0_CLIENT_SECRET]
REQUIRED_EXECUTABLES = %w[git-crypt terraform helm aws ssh-keygen]
REQUIRED_AWS_PROFILES = %w[moj-cp]
LIVE_CLUSTER_NAME_REXP = /live/
AWS_REGION = "eu-west-2"
require "open3"
require "optparse"
class ClusterDeleter
attr_reader :options
def initialize(options)
@options = options
end
def run
puts "DRY-RUN: No changes will be made to the cluster" if dry_run?
check_prerequisites
target_eks_cluster
abort_if_user_namespaces_exist
terraform_eks_components
terraform_base_eks
terraform_vpc if destroy_vpc?
terraform_workspaces_eks
end
private
def kind
options[:kind]
end
def dry_run?
!!options[:dry_run]
end
def destroy_vpc?
!!options[:destroy_vpc]
end
def check_prerequisites
running_in_docker
check_options
check_env_vars
check_software_installed
check_aws_profiles
check_name_length
end
def running_in_docker
unless running_in_docker?
raise "This script may only be run inside a docker container"
end
end
# https://stackoverflow.com/questions/20010199/how-to-determine-if-a-process-runs-inside-lxc-docker
def running_in_docker?
FileTest.exists?("/.dockerenv") || (
FileTest.exists?("/proc/self/cgroup") &&
File.read("/proc/self/cgroup").split("\n").any?
)
end
def check_options
raise "No cluster name supplied" if cluster_name.nil?
raise "You may not destroy production clusters" if LIVE_CLUSTER_NAME_REXP.match(cluster_name)
end
def check_env_vars
REQUIRED_ENV_VARS.each do |var|
value = ENV.fetch(var, "")
raise "ERROR Required environment variable #{var} is not set." if value.empty?
end
end
def check_software_installed
REQUIRED_EXECUTABLES.each do |exe|
raise "ERROR Required executable #{exe} not found." unless system("which #{exe}")
end
end
def check_aws_profiles
creds = File.read("#{ENV.fetch("HOME")}/.aws/credentials").split("\n")
REQUIRED_AWS_PROFILES.each do |profile|
raise "ERROR Required AWS Profile #{profile} not found." \
unless creds.grep(/\[#{profile}\]/).any?
end
end
def check_name_length
l = cluster_name.length
raise "ERROR Cluster name #{cluster_name} too long (#{l} chars). Max. is #{MAX_CLUSTER_NAME_LENGTH}." \
unless l <= MAX_CLUSTER_NAME_LENGTH
end
def target_eks_cluster
execute("aws eks update-kubeconfig --name #{cluster_name} --region #{AWS_REGION}")
end
# If someone has deployed something into this cluster, there might be
# associated AWS resources which would be left orphaned if the cluster were
# destroyed. So, we check for any unexpected namespaces, and abort if we find
# any.
def abort_if_user_namespaces_exist
if user_namespaces.any?
puts "\nPlease delete these namespaces, and any associated AWS resources, before destroying this cluster:"
user_namespaces.each { |ns| puts " * #{ns}" }
puts
raise
end
end
def user_namespaces
stdout, _, _ = execute("kubectl get ns -o name | sed 's/namespace.//'", true)
namespaces = stdout.split("\n")
smoketest_ns = namespaces.find_all { |ns| ns.match?(/smoketest-.*/) }
namespaces - EKS_SYSTEM_NAMESPACES - smoketest_ns
end
def terraform_eks_components
dir = "terraform/aws-accounts/cloud-platform-aws/vpc/eks/components"
tf_init dir
tf_workspace_select(dir, cluster_name)
tf_destroy(dir)
end
def terraform_base_eks
dir = "terraform/aws-accounts/cloud-platform-aws/vpc/eks"
tf_init dir
tf_workspace_select(dir, cluster_name)
tf_destroy(dir)
end
def terraform_workspaces_eks
["terraform/aws-accounts/cloud-platform-aws/vpc/eks", "terraform/aws-accounts/cloud-platform-aws/vpc/eks/components", "terraform/aws-accounts/cloud-platform-aws/vpc"].each do |dir|
execute "cd #{dir}; terraform workspace select default; terraform workspace delete #{cluster_name}"
end
end
def terraform_vpc
dir = "terraform/aws-accounts/cloud-platform-aws/vpc"
tf_init dir
tf_workspace_select(dir, vpc_name)
tf_destroy(dir)
end
def tf_init(dir)
execute "cd #{dir}; terraform init"
end
def tf_workspace_select(dir, workspace)
execute "cd #{dir}; terraform workspace select #{workspace}"
end
def tf_destroy(dir, target = nil)
tgt = target.nil? ? "" : "-target #{target}"
execute "cd #{dir}; terraform destroy #{tgt} -auto-approve"
end
def vpc_name
options[:vpc_name]
end
def cluster_name
options[:cluster_name]
end
def cluster_long_name
"#{cluster_name}.cloud-platform.service.justice.gov.uk"
end
def execute(cmd, execute_in_dry_run = false)
if dry_run? && !execute_in_dry_run
puts "DRY-RUN: #{cmd}"
nil
else
puts "executing: #{cmd}"
stdout, stderr, status = Open3.capture3(cmd)
unless status.success?
puts "Command: #{cmd} failed."
puts stderr
raise
end
puts stdout
[stdout, stderr, status]
end
end
end
def parse_options
# set defaults
options = {
dry_run: true,
cluster_name: nil,
destroy_vpc: true
}
OptionParser.new { |opts|
opts.on("-n", "--name CLUSTER-NAME", "Cluster name (max. #{MAX_CLUSTER_NAME_LENGTH} chars)") do |name|
options[:cluster_name] = name.sub(".cloud-platform.service.justice.gov.uk", "")
end
opts.on("-v", "--vpc-name VPC-NAME", "VPC to destroy, defaults to CLUSTER-NAME") do |name|
options[:vpc_name] = name
end
opts.on("-d", "--dont-destroy-vpc", "Supply this flag to leave the VPC intact") do |dont_destroy_vpc|
options[:destroy_vpc] = !dont_destroy_vpc
end
opts.on("-y", "--yes", "Actually destroy the cluster") do |yes|
options[:dry_run] = !yes
end
opts.on_tail("-h", "--help", "Show help message") do
puts opts
exit
end
}.parse!
# By default, we name our VPCs the same as the cluster
options[:vpc_name] ||= options[:cluster_name]
options
end
############################################################
options = parse_options
ClusterDeleter.new(options).run