floe 0.10.0 → 0.11.1
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/CHANGELOG.md +27 -1
- data/exe/floe +35 -28
- data/lib/floe/container_runner/docker.rb +225 -0
- data/lib/floe/container_runner/docker_mixin.rb +32 -0
- data/lib/floe/container_runner/kubernetes.rb +329 -0
- data/lib/floe/container_runner/podman.rb +104 -0
- data/lib/floe/container_runner.rb +61 -0
- data/lib/floe/runner.rb +82 -0
- data/lib/floe/version.rb +1 -1
- data/lib/floe/workflow/context.rb +3 -1
- data/lib/floe/workflow/states/non_terminal_mixin.rb +3 -1
- data/lib/floe/workflow/states/pass.rb +2 -3
- data/lib/floe/workflow/states/task.rb +2 -7
- data/lib/floe.rb +2 -18
- metadata +8 -7
- data/lib/floe/workflow/runner/docker.rb +0 -227
- data/lib/floe/workflow/runner/docker_mixin.rb +0 -32
- data/lib/floe/workflow/runner/kubernetes.rb +0 -331
- data/lib/floe/workflow/runner/podman.rb +0 -106
- data/lib/floe/workflow/runner.rb +0 -77
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: floe
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.11.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ManageIQ Developers
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-05-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: awesome_spawn
|
@@ -185,8 +185,14 @@ files:
|
|
185
185
|
- exe/floe
|
186
186
|
- floe.gemspec
|
187
187
|
- lib/floe.rb
|
188
|
+
- lib/floe/container_runner.rb
|
189
|
+
- lib/floe/container_runner/docker.rb
|
190
|
+
- lib/floe/container_runner/docker_mixin.rb
|
191
|
+
- lib/floe/container_runner/kubernetes.rb
|
192
|
+
- lib/floe/container_runner/podman.rb
|
188
193
|
- lib/floe/logging.rb
|
189
194
|
- lib/floe/null_logger.rb
|
195
|
+
- lib/floe/runner.rb
|
190
196
|
- lib/floe/version.rb
|
191
197
|
- lib/floe/workflow.rb
|
192
198
|
- lib/floe/workflow/catcher.rb
|
@@ -200,11 +206,6 @@ files:
|
|
200
206
|
- lib/floe/workflow/payload_template.rb
|
201
207
|
- lib/floe/workflow/reference_path.rb
|
202
208
|
- lib/floe/workflow/retrier.rb
|
203
|
-
- lib/floe/workflow/runner.rb
|
204
|
-
- lib/floe/workflow/runner/docker.rb
|
205
|
-
- lib/floe/workflow/runner/docker_mixin.rb
|
206
|
-
- lib/floe/workflow/runner/kubernetes.rb
|
207
|
-
- lib/floe/workflow/runner/podman.rb
|
208
209
|
- lib/floe/workflow/state.rb
|
209
210
|
- lib/floe/workflow/states/choice.rb
|
210
211
|
- lib/floe/workflow/states/fail.rb
|
@@ -1,227 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Floe
|
4
|
-
class Workflow
|
5
|
-
class Runner
|
6
|
-
class Docker < Floe::Workflow::Runner
|
7
|
-
include DockerMixin
|
8
|
-
|
9
|
-
DOCKER_COMMAND = "docker"
|
10
|
-
|
11
|
-
def initialize(options = {})
|
12
|
-
require "awesome_spawn"
|
13
|
-
require "io/wait"
|
14
|
-
require "tempfile"
|
15
|
-
|
16
|
-
super
|
17
|
-
|
18
|
-
@network = options.fetch("network", "bridge")
|
19
|
-
@pull_policy = options["pull-policy"]
|
20
|
-
end
|
21
|
-
|
22
|
-
def run_async!(resource, env = {}, secrets = {})
|
23
|
-
raise ArgumentError, "Invalid resource" unless resource&.start_with?("docker://")
|
24
|
-
|
25
|
-
image = resource.sub("docker://", "")
|
26
|
-
|
27
|
-
runner_context = {}
|
28
|
-
|
29
|
-
if secrets && !secrets.empty?
|
30
|
-
runner_context["secrets_ref"] = create_secret(secrets)
|
31
|
-
end
|
32
|
-
|
33
|
-
begin
|
34
|
-
runner_context["container_ref"] = run_container(image, env, runner_context["secrets_ref"])
|
35
|
-
runner_context
|
36
|
-
rescue AwesomeSpawn::CommandResultError => err
|
37
|
-
cleanup(runner_context)
|
38
|
-
{"Error" => "States.TaskFailed", "Cause" => err.to_s}
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def cleanup(runner_context)
|
43
|
-
container_id, secrets_file = runner_context.values_at("container_ref", "secrets_ref")
|
44
|
-
|
45
|
-
delete_container(container_id) if container_id
|
46
|
-
delete_secret(secrets_file) if secrets_file
|
47
|
-
end
|
48
|
-
|
49
|
-
def wait(timeout: nil, events: %i[create update delete], &block)
|
50
|
-
until_timestamp = Time.now.utc + timeout if timeout
|
51
|
-
|
52
|
-
r, w = IO.pipe
|
53
|
-
|
54
|
-
pid = AwesomeSpawn.run_detached(
|
55
|
-
self.class::DOCKER_COMMAND, :err => :out, :out => w, :params => wait_params(until_timestamp)
|
56
|
-
)
|
57
|
-
|
58
|
-
w.close
|
59
|
-
|
60
|
-
loop do
|
61
|
-
readable_timeout = until_timestamp - Time.now.utc if until_timestamp
|
62
|
-
|
63
|
-
# Wait for our end of the pipe to be readable and if it didn't timeout
|
64
|
-
# get the events from stdout
|
65
|
-
next if r.wait_readable(readable_timeout).nil?
|
66
|
-
|
67
|
-
# Get all events while the pipe is readable
|
68
|
-
notices = []
|
69
|
-
while r.ready?
|
70
|
-
notice = r.gets
|
71
|
-
|
72
|
-
# If the process has exited `r.gets` returns `nil` and the pipe is
|
73
|
-
# always `ready?`
|
74
|
-
break if notice.nil?
|
75
|
-
|
76
|
-
event, runner_context = parse_notice(notice)
|
77
|
-
next if event.nil? || !events.include?(event)
|
78
|
-
|
79
|
-
notices << [event, runner_context]
|
80
|
-
end
|
81
|
-
|
82
|
-
# If we're given a block yield the events otherwise return them
|
83
|
-
if block
|
84
|
-
notices.each(&block)
|
85
|
-
else
|
86
|
-
# Terminate the `docker events` process before returning the events
|
87
|
-
sigterm(pid)
|
88
|
-
|
89
|
-
return notices
|
90
|
-
end
|
91
|
-
|
92
|
-
# Check that the `docker events` process is still alive
|
93
|
-
Process.kill(0, pid)
|
94
|
-
rescue Errno::ESRCH
|
95
|
-
# Break out of the loop if the `docker events` process has exited
|
96
|
-
break
|
97
|
-
end
|
98
|
-
ensure
|
99
|
-
r.close
|
100
|
-
end
|
101
|
-
|
102
|
-
def status!(runner_context)
|
103
|
-
return if runner_context.key?("Error")
|
104
|
-
|
105
|
-
runner_context["container_state"] = inspect_container(runner_context["container_ref"])&.dig("State")
|
106
|
-
end
|
107
|
-
|
108
|
-
def running?(runner_context)
|
109
|
-
!!runner_context.dig("container_state", "Running")
|
110
|
-
end
|
111
|
-
|
112
|
-
def success?(runner_context)
|
113
|
-
runner_context.dig("container_state", "ExitCode") == 0
|
114
|
-
end
|
115
|
-
|
116
|
-
def output(runner_context)
|
117
|
-
return runner_context.slice("Error", "Cause") if runner_context.key?("Error")
|
118
|
-
|
119
|
-
output = docker!("logs", runner_context["container_ref"], :combined_output => true).output
|
120
|
-
runner_context["output"] = output
|
121
|
-
end
|
122
|
-
|
123
|
-
private
|
124
|
-
|
125
|
-
attr_reader :network
|
126
|
-
|
127
|
-
def run_container(image, env, secrets_file)
|
128
|
-
params = run_container_params(image, env, secrets_file)
|
129
|
-
|
130
|
-
logger.debug("Running #{AwesomeSpawn.build_command_line(self.class::DOCKER_COMMAND, params)}")
|
131
|
-
|
132
|
-
result = docker!(*params)
|
133
|
-
result.output
|
134
|
-
end
|
135
|
-
|
136
|
-
def run_container_params(image, env, secrets_file)
|
137
|
-
params = ["run"]
|
138
|
-
params << :detach
|
139
|
-
params += env.map { |k, v| [:e, "#{k}=#{v}"] }
|
140
|
-
params << [:e, "_CREDENTIALS=/run/secrets"] if secrets_file
|
141
|
-
params << [:pull, @pull_policy] if @pull_policy
|
142
|
-
params << [:net, "host"] if @network == "host"
|
143
|
-
params << [:v, "#{secrets_file}:/run/secrets:z"] if secrets_file
|
144
|
-
params << [:name, container_name(image)]
|
145
|
-
params << image
|
146
|
-
end
|
147
|
-
|
148
|
-
def wait_params(until_timestamp)
|
149
|
-
params = ["events", [:format, "{{json .}}"], [:filter, "type=container"], [:since, Time.now.utc.to_i]]
|
150
|
-
params << [:until, until_timestamp.to_i] if until_timestamp
|
151
|
-
params
|
152
|
-
end
|
153
|
-
|
154
|
-
def parse_notice(notice)
|
155
|
-
notice = JSON.parse(notice)
|
156
|
-
|
157
|
-
status = notice["status"]
|
158
|
-
event = docker_event_status_to_event(status)
|
159
|
-
running = event != :delete
|
160
|
-
|
161
|
-
name, exit_code = notice.dig("Actor", "Attributes")&.values_at("name", "exitCode")
|
162
|
-
|
163
|
-
runner_context = {"container_ref" => name, "container_state" => {"Running" => running, "ExitCode" => exit_code.to_i}}
|
164
|
-
|
165
|
-
[event, runner_context]
|
166
|
-
rescue JSON::ParserError
|
167
|
-
[]
|
168
|
-
end
|
169
|
-
|
170
|
-
def docker_event_status_to_event(status)
|
171
|
-
case status
|
172
|
-
when "create"
|
173
|
-
:create
|
174
|
-
when "start"
|
175
|
-
:update
|
176
|
-
when "die", "destroy"
|
177
|
-
:delete
|
178
|
-
else
|
179
|
-
:unkonwn
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
def inspect_container(container_id)
|
184
|
-
JSON.parse(docker!("inspect", container_id).output).first
|
185
|
-
rescue
|
186
|
-
nil
|
187
|
-
end
|
188
|
-
|
189
|
-
def delete_container(container_id)
|
190
|
-
docker!("rm", container_id)
|
191
|
-
rescue
|
192
|
-
nil
|
193
|
-
end
|
194
|
-
|
195
|
-
def delete_secret(secrets_file)
|
196
|
-
return unless File.exist?(secrets_file)
|
197
|
-
|
198
|
-
File.unlink(secrets_file)
|
199
|
-
rescue
|
200
|
-
nil
|
201
|
-
end
|
202
|
-
|
203
|
-
def create_secret(secrets)
|
204
|
-
secrets_file = Tempfile.new
|
205
|
-
secrets_file.write(secrets.to_json)
|
206
|
-
secrets_file.close
|
207
|
-
secrets_file.path
|
208
|
-
end
|
209
|
-
|
210
|
-
def sigterm(pid)
|
211
|
-
Process.kill("TERM", pid)
|
212
|
-
rescue Errno::ESRCH
|
213
|
-
nil
|
214
|
-
end
|
215
|
-
|
216
|
-
def global_docker_options
|
217
|
-
[]
|
218
|
-
end
|
219
|
-
|
220
|
-
def docker!(*args, **kwargs)
|
221
|
-
params = global_docker_options + args
|
222
|
-
AwesomeSpawn.run!(self.class::DOCKER_COMMAND, :params => params, **kwargs)
|
223
|
-
end
|
224
|
-
end
|
225
|
-
end
|
226
|
-
end
|
227
|
-
end
|
@@ -1,32 +0,0 @@
|
|
1
|
-
module Floe
|
2
|
-
class Workflow
|
3
|
-
class Runner
|
4
|
-
module DockerMixin
|
5
|
-
def image_name(image)
|
6
|
-
image.match(%r{^(?<repository>.+/)?(?<image>.+):(?<tag>.+)$})&.named_captures&.dig("image")
|
7
|
-
end
|
8
|
-
|
9
|
-
# 63 is the max kubernetes pod name length
|
10
|
-
# -5 for the "floe-" prefix
|
11
|
-
# -9 for the random hex suffix and leading hyphen
|
12
|
-
MAX_CONTAINER_NAME_SIZE = 63 - 5 - 9
|
13
|
-
|
14
|
-
def container_name(image)
|
15
|
-
name = image_name(image)
|
16
|
-
raise ArgumentError, "Invalid docker image [#{image}]" if name.nil?
|
17
|
-
|
18
|
-
# Normalize the image name to be used in the container name.
|
19
|
-
# This follows RFC 1123 Label names in Kubernetes as they are the most restrictive
|
20
|
-
# See https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names
|
21
|
-
# and https://github.com/kubernetes/kubernetes/blob/952a9cb0/staging/src/k8s.io/apimachinery/pkg/util/validation/validation.go#L178-L184
|
22
|
-
#
|
23
|
-
# This does not follow the leading and trailing character restriction because we will embed it
|
24
|
-
# below with a prefix and suffix that already conform to the RFC.
|
25
|
-
normalized_name = name.downcase.gsub(/[^a-z0-9-]/, "-")[0, MAX_CONTAINER_NAME_SIZE]
|
26
|
-
|
27
|
-
"floe-#{normalized_name}-#{SecureRandom.hex(4)}"
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
@@ -1,331 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Floe
|
4
|
-
class Workflow
|
5
|
-
class Runner
|
6
|
-
class Kubernetes < Floe::Workflow::Runner
|
7
|
-
include DockerMixin
|
8
|
-
|
9
|
-
TOKEN_FILE = "/run/secrets/kubernetes.io/serviceaccount/token"
|
10
|
-
CA_CERT_FILE = "/run/secrets/kubernetes.io/serviceaccount/ca.crt"
|
11
|
-
RUNNING_PHASES = %w[Pending Running].freeze
|
12
|
-
FAILURE_REASONS = %w[CrashLoopBackOff ImagePullBackOff ErrImagePull].freeze
|
13
|
-
|
14
|
-
def initialize(options = {})
|
15
|
-
require "active_support/core_ext/hash/keys"
|
16
|
-
require "awesome_spawn"
|
17
|
-
require "securerandom"
|
18
|
-
require "base64"
|
19
|
-
require "kubeclient"
|
20
|
-
require "yaml"
|
21
|
-
|
22
|
-
@kubeconfig_file = ENV.fetch("KUBECONFIG", nil) || options.fetch("kubeconfig", File.join(Dir.home, ".kube", "config"))
|
23
|
-
@kubeconfig_context = options["kubeconfig_context"]
|
24
|
-
|
25
|
-
@token = options["token"]
|
26
|
-
@token ||= File.read(options["token_file"]) if options.key?("token_file")
|
27
|
-
@token ||= File.read(TOKEN_FILE) if File.exist?(TOKEN_FILE)
|
28
|
-
|
29
|
-
@server = options["server"]
|
30
|
-
@server ||= URI::HTTPS.build(:host => ENV.fetch("KUBERNETES_SERVICE_HOST"), :port => ENV.fetch("KUBERNETES_SERVICE_PORT", 6443)) if ENV.key?("KUBERNETES_SERVICE_HOST")
|
31
|
-
|
32
|
-
@ca_file = options["ca_file"]
|
33
|
-
@ca_file ||= CA_CERT_FILE if File.exist?(CA_CERT_FILE)
|
34
|
-
|
35
|
-
@verify_ssl = options["verify_ssl"] == "false" ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
|
36
|
-
|
37
|
-
if server.nil? && token.nil? && !File.exist?(kubeconfig_file)
|
38
|
-
raise ArgumentError, "Missing connections options, provide a kubeconfig file or pass server and token via --docker-runner-options"
|
39
|
-
end
|
40
|
-
|
41
|
-
@namespace = options.fetch("namespace", "default")
|
42
|
-
|
43
|
-
@pull_policy = options["pull-policy"]
|
44
|
-
@task_service_account = options["task_service_account"]
|
45
|
-
|
46
|
-
super
|
47
|
-
end
|
48
|
-
|
49
|
-
def run_async!(resource, env = {}, secrets = {})
|
50
|
-
raise ArgumentError, "Invalid resource" unless resource&.start_with?("docker://")
|
51
|
-
|
52
|
-
image = resource.sub("docker://", "")
|
53
|
-
name = container_name(image)
|
54
|
-
secret = create_secret!(secrets) if secrets && !secrets.empty?
|
55
|
-
|
56
|
-
runner_context = {"container_ref" => name, "container_state" => {"phase" => "Pending"}, "secrets_ref" => secret}
|
57
|
-
|
58
|
-
begin
|
59
|
-
create_pod!(name, image, env, secret)
|
60
|
-
runner_context
|
61
|
-
rescue Kubeclient::HttpError => err
|
62
|
-
cleanup(runner_context)
|
63
|
-
{"Error" => "States.TaskFailed", "Cause" => err.to_s}
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
def status!(runner_context)
|
68
|
-
return if runner_context.key?("Error")
|
69
|
-
|
70
|
-
runner_context["container_state"] = pod_info(runner_context["container_ref"]).to_h.deep_stringify_keys["status"]
|
71
|
-
end
|
72
|
-
|
73
|
-
def running?(runner_context)
|
74
|
-
return false unless pod_running?(runner_context)
|
75
|
-
# If a pod is Pending and the containers are waiting with a failure
|
76
|
-
# reason such as ImagePullBackOff or CrashLoopBackOff then the pod
|
77
|
-
# will never be run.
|
78
|
-
return false if container_failed?(runner_context)
|
79
|
-
|
80
|
-
true
|
81
|
-
end
|
82
|
-
|
83
|
-
def success?(runner_context)
|
84
|
-
runner_context.dig("container_state", "phase") == "Succeeded"
|
85
|
-
end
|
86
|
-
|
87
|
-
def output(runner_context)
|
88
|
-
if runner_context.key?("Error")
|
89
|
-
runner_context.slice("Error", "Cause")
|
90
|
-
elsif container_failed?(runner_context)
|
91
|
-
failed_state = failed_container_states(runner_context).first
|
92
|
-
{"Error" => failed_state["reason"], "Cause" => failed_state["message"]}
|
93
|
-
else
|
94
|
-
runner_context["output"] = kubeclient.get_pod_log(runner_context["container_ref"], namespace).body
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
def cleanup(runner_context)
|
99
|
-
pod, secret = runner_context.values_at("container_ref", "secrets_ref")
|
100
|
-
|
101
|
-
delete_pod(pod) if pod
|
102
|
-
delete_secret(secret) if secret
|
103
|
-
end
|
104
|
-
|
105
|
-
def wait(timeout: nil, events: %i[create update delete])
|
106
|
-
retry_connection = true
|
107
|
-
|
108
|
-
begin
|
109
|
-
watcher = kubeclient.watch_pods(:namespace => namespace)
|
110
|
-
|
111
|
-
retry_connection = true
|
112
|
-
|
113
|
-
if timeout.to_i > 0
|
114
|
-
timeout_thread = Thread.new do
|
115
|
-
sleep(timeout)
|
116
|
-
watcher.finish
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
watcher.each do |notice|
|
121
|
-
break if error_notice?(notice)
|
122
|
-
|
123
|
-
event = kube_notice_type_to_event(notice.type)
|
124
|
-
next unless events.include?(event)
|
125
|
-
|
126
|
-
runner_context = parse_notice(notice)
|
127
|
-
next if runner_context.nil?
|
128
|
-
|
129
|
-
if block_given?
|
130
|
-
yield [event, runner_context]
|
131
|
-
else
|
132
|
-
timeout_thread&.kill # If we break out before the timeout, kill the timeout thread
|
133
|
-
return [[event, runner_context]]
|
134
|
-
end
|
135
|
-
end
|
136
|
-
rescue Kubeclient::HttpError => err
|
137
|
-
raise unless err.error_code == 401 && retry_connection
|
138
|
-
|
139
|
-
@kubeclient = nil
|
140
|
-
retry_connection = false
|
141
|
-
retry
|
142
|
-
ensure
|
143
|
-
begin
|
144
|
-
watch&.finish
|
145
|
-
rescue
|
146
|
-
nil
|
147
|
-
end
|
148
|
-
|
149
|
-
timeout_thread&.join(0)
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
private
|
154
|
-
|
155
|
-
attr_reader :ca_file, :kubeconfig_file, :kubeconfig_context, :namespace, :server, :token, :verify_ssl
|
156
|
-
|
157
|
-
def pod_info(pod_name)
|
158
|
-
kubeclient.get_pod(pod_name, namespace)
|
159
|
-
end
|
160
|
-
|
161
|
-
def pod_running?(context)
|
162
|
-
RUNNING_PHASES.include?(context.dig("container_state", "phase"))
|
163
|
-
end
|
164
|
-
|
165
|
-
def failed_container_states(context)
|
166
|
-
container_statuses = context.dig("container_state", "containerStatuses") || []
|
167
|
-
container_statuses.filter_map { |status| status["state"]&.values&.first }
|
168
|
-
.select { |state| FAILURE_REASONS.include?(state["reason"]) }
|
169
|
-
end
|
170
|
-
|
171
|
-
def container_failed?(context)
|
172
|
-
failed_container_states(context).any?
|
173
|
-
end
|
174
|
-
|
175
|
-
def pod_spec(name, image, env, secret = nil)
|
176
|
-
spec = {
|
177
|
-
:kind => "Pod",
|
178
|
-
:apiVersion => "v1",
|
179
|
-
:metadata => {
|
180
|
-
:name => name,
|
181
|
-
:namespace => namespace
|
182
|
-
},
|
183
|
-
:spec => {
|
184
|
-
:containers => [
|
185
|
-
{
|
186
|
-
:name => name[0...-9], # remove the random suffix and its leading hyphen
|
187
|
-
:image => image,
|
188
|
-
:env => env.map { |k, v| {:name => k, :value => v.to_s} }
|
189
|
-
}
|
190
|
-
],
|
191
|
-
:restartPolicy => "Never"
|
192
|
-
}
|
193
|
-
}
|
194
|
-
|
195
|
-
spec[:spec][:imagePullPolicy] = @pull_policy if @pull_policy
|
196
|
-
spec[:spec][:serviceAccountName] = @task_service_account if @task_service_account
|
197
|
-
|
198
|
-
if secret
|
199
|
-
spec[:spec][:volumes] = [
|
200
|
-
{
|
201
|
-
:name => "secret-volume",
|
202
|
-
:secret => {:secretName => secret}
|
203
|
-
}
|
204
|
-
]
|
205
|
-
|
206
|
-
spec[:spec][:containers][0][:env] << {
|
207
|
-
:name => "_CREDENTIALS",
|
208
|
-
:value => "/run/secrets/#{secret}/secret"
|
209
|
-
}
|
210
|
-
|
211
|
-
spec[:spec][:containers][0][:volumeMounts] = [
|
212
|
-
{
|
213
|
-
:name => "secret-volume",
|
214
|
-
:mountPath => "/run/secrets/#{secret}",
|
215
|
-
:readOnly => true
|
216
|
-
}
|
217
|
-
]
|
218
|
-
end
|
219
|
-
|
220
|
-
spec
|
221
|
-
end
|
222
|
-
|
223
|
-
def create_pod!(name, image, env, secret = nil)
|
224
|
-
kubeclient.create_pod(pod_spec(name, image, env, secret))
|
225
|
-
end
|
226
|
-
|
227
|
-
def delete_pod!(name)
|
228
|
-
kubeclient.delete_pod(name, namespace)
|
229
|
-
end
|
230
|
-
|
231
|
-
def delete_pod(name)
|
232
|
-
delete_pod!(name)
|
233
|
-
rescue
|
234
|
-
nil
|
235
|
-
end
|
236
|
-
|
237
|
-
def create_secret!(secrets)
|
238
|
-
secret_name = SecureRandom.uuid
|
239
|
-
|
240
|
-
secret_config = {
|
241
|
-
:kind => "Secret",
|
242
|
-
:apiVersion => "v1",
|
243
|
-
:metadata => {
|
244
|
-
:name => secret_name,
|
245
|
-
:namespace => namespace
|
246
|
-
},
|
247
|
-
:data => {
|
248
|
-
:secret => Base64.urlsafe_encode64(secrets.to_json)
|
249
|
-
},
|
250
|
-
:type => "Opaque"
|
251
|
-
}
|
252
|
-
|
253
|
-
kubeclient.create_secret(secret_config)
|
254
|
-
|
255
|
-
secret_name
|
256
|
-
end
|
257
|
-
|
258
|
-
def delete_secret!(secret_name)
|
259
|
-
kubeclient.delete_secret(secret_name, namespace)
|
260
|
-
end
|
261
|
-
|
262
|
-
def delete_secret(name)
|
263
|
-
delete_secret!(name)
|
264
|
-
rescue
|
265
|
-
nil
|
266
|
-
end
|
267
|
-
|
268
|
-
def kube_notice_type_to_event(type)
|
269
|
-
case type
|
270
|
-
when "ADDED"
|
271
|
-
:create
|
272
|
-
when "MODIFIED"
|
273
|
-
:update
|
274
|
-
when "DELETED"
|
275
|
-
:delete
|
276
|
-
else
|
277
|
-
:unknown
|
278
|
-
end
|
279
|
-
end
|
280
|
-
|
281
|
-
def error_notice?(notice)
|
282
|
-
return false unless notice.type == "ERROR"
|
283
|
-
|
284
|
-
message = notice.object&.message
|
285
|
-
code = notice.object&.code
|
286
|
-
reason = notice.object&.reason
|
287
|
-
|
288
|
-
logger.warn("Received [#{code} #{reason}], [#{message}]")
|
289
|
-
|
290
|
-
true
|
291
|
-
end
|
292
|
-
|
293
|
-
def parse_notice(notice)
|
294
|
-
return if notice.object.nil?
|
295
|
-
|
296
|
-
pod = notice.object
|
297
|
-
container_ref = pod.metadata.name
|
298
|
-
container_state = pod.to_h[:status].deep_stringify_keys
|
299
|
-
|
300
|
-
{"container_ref" => container_ref, "container_state" => container_state}
|
301
|
-
end
|
302
|
-
|
303
|
-
def kubeclient
|
304
|
-
return @kubeclient unless @kubeclient.nil?
|
305
|
-
|
306
|
-
if server && token
|
307
|
-
api_endpoint = server
|
308
|
-
auth_options = {:bearer_token => token}
|
309
|
-
ssl_options = {:verify_ssl => verify_ssl}
|
310
|
-
ssl_options[:ca_file] = ca_file if ca_file
|
311
|
-
else
|
312
|
-
context = kubeconfig&.context(kubeconfig_context)
|
313
|
-
raise ArgumentError, "Missing connections options, provide a kubeconfig file or pass server and token via --docker-runner-options" if context.nil?
|
314
|
-
|
315
|
-
api_endpoint = context.api_endpoint
|
316
|
-
auth_options = context.auth_options
|
317
|
-
ssl_options = context.ssl_options
|
318
|
-
end
|
319
|
-
|
320
|
-
@kubeclient = Kubeclient::Client.new(api_endpoint, "v1", :ssl_options => ssl_options, :auth_options => auth_options).tap(&:discover)
|
321
|
-
end
|
322
|
-
|
323
|
-
def kubeconfig
|
324
|
-
return if kubeconfig_file.nil? || !File.exist?(kubeconfig_file)
|
325
|
-
|
326
|
-
Kubeclient::Config.read(kubeconfig_file)
|
327
|
-
end
|
328
|
-
end
|
329
|
-
end
|
330
|
-
end
|
331
|
-
end
|