Class: Controlplane

Inherits:
Object
  • Object
show all
Defined in:
lib/core/controlplane.rb

Overview

rubocop:disable Metrics/ClassLength

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config) ⇒ Controlplane

Returns a new instance of Controlplane.



6
7
8
9
10
11
# File 'lib/core/controlplane.rb', line 6

def initialize(config)
  @config = config
  @api = ControlplaneApi.new
  @gvc = config.app
  @org = config.org
end

Instance Attribute Details

#apiObject (readonly)

Returns the value of attribute api.



4
5
6
# File 'lib/core/controlplane.rb', line 4

def api
  @api
end

#configObject (readonly)

Returns the value of attribute config.



4
5
6
# File 'lib/core/controlplane.rb', line 4

def config
  @config
end

#gvcObject (readonly)

Returns the value of attribute gvc.



4
5
6
# File 'lib/core/controlplane.rb', line 4

def gvc
  @gvc
end

#orgObject (readonly)

Returns the value of attribute org.



4
5
6
# File 'lib/core/controlplane.rb', line 4

def org
  @org
end

Instance Method Details

#apply_hash(data) ⇒ Object



343
344
345
# File 'lib/core/controlplane.rb', line 343

def apply_hash(data)
  apply_template(data.to_yaml)
end

#apply_template(data) ⇒ Object

apply



322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# File 'lib/core/controlplane.rb', line 322

def apply_template(data) # rubocop:disable Metrics/MethodLength
  Tempfile.create do |f|
    f.write(data)
    f.rewind
    cmd = "cpln apply #{gvc_org} --file #{f.path}"
    if Shell.tmp_stderr
      cmd += " 2> #{Shell.tmp_stderr.path}" if Shell.should_hide_output?

      Shell.debug("CMD", cmd)

      result = `#{cmd}`
      $CHILD_STATUS.success? ? parse_apply_result(result) : false
    else
      Shell.debug("CMD", cmd)

      result = `#{cmd}`
      $CHILD_STATUS.success? ? parse_apply_result(result) : exit(1)
    end
  end
end

#bind_identity_to_policy(identity_link, policy) ⇒ Object



315
316
317
318
319
# File 'lib/core/controlplane.rb', line 315

def bind_identity_to_policy(identity_link, policy)
  cmd = "cpln policy add-binding #{policy} --org #{org} --identity #{identity_link} --permission reveal"
  cmd += " > /dev/null" if Shell.should_hide_output?
  perform!(cmd)
end

#delete_volumeset(volumeset, a_gvc = gvc) ⇒ Object



246
247
248
# File 'lib/core/controlplane.rb', line 246

def delete_volumeset(volumeset, a_gvc = gvc)
  api.delete_volumeset(org: org, gvc: a_gvc, volumeset: volumeset)
end

#delete_workload(workload, a_gvc = gvc) ⇒ Object



222
223
224
# File 'lib/core/controlplane.rb', line 222

def delete_workload(workload, a_gvc = gvc)
  api.delete_workload(org: org, gvc: a_gvc, workload: workload)
end

#domain_workload_matches?(data, workload) ⇒ Boolean

Returns:

  • (Boolean)


280
281
282
283
# File 'lib/core/controlplane.rb', line 280

def domain_workload_matches?(data, workload)
  route = find_domain_route(data)
  route["workloadLink"].match?(%r{/org/#{org}/gvc/#{gvc}/workload/#{workload}})
end

#fetch_domain(domain) ⇒ Object



272
273
274
275
276
277
278
# File 'lib/core/controlplane.rb', line 272

def fetch_domain(domain)
  domain_data = api.fetch_domain(org: org, domain: domain)
  route = find_domain_route(domain_data)
  return nil if route.nil?

  domain_data
end

#fetch_gvc(a_gvc = gvc, a_org = org) ⇒ Object



104
105
106
# File 'lib/core/controlplane.rb', line 104

def fetch_gvc(a_gvc = gvc, a_org = org)
  api.gvc_get(gvc: a_gvc, org: a_org)
end

#fetch_gvc!(a_gvc = gvc) ⇒ Object



108
109
110
111
112
113
# File 'lib/core/controlplane.rb', line 108

def fetch_gvc!(a_gvc = gvc)
  gvc_data = fetch_gvc(a_gvc)
  return gvc_data if gvc_data

  raise "Can't find app '#{gvc}', please create it with 'cpl setup-app -a #{config.app}'."
end

#fetch_gvcsObject

gvc



91
92
93
# File 'lib/core/controlplane.rb', line 91

def fetch_gvcs
  api.gvc_list(org: org)
end

#fetch_identity(identity, a_gvc = gvc) ⇒ Object

identities



305
306
307
# File 'lib/core/controlplane.rb', line 305

def fetch_identity(identity, a_gvc = gvc)
  api.fetch_identity(org: org, gvc: a_gvc, identity: identity)
end

#fetch_policy(policy) ⇒ Object

policies



311
312
313
# File 'lib/core/controlplane.rb', line 311

def fetch_policy(policy)
  api.fetch_policy(org: org, policy: policy)
end

#fetch_volumesets(a_gvc = gvc) ⇒ Object

volumeset



242
243
244
# File 'lib/core/controlplane.rb', line 242

def fetch_volumesets(a_gvc = gvc)
  api.list_volumesets(org: org, gvc: a_gvc)
end

#fetch_workload(workload) ⇒ Object



129
130
131
# File 'lib/core/controlplane.rb', line 129

def fetch_workload(workload)
  api.workload_get(workload: workload, gvc: gvc, org: org)
end

#fetch_workload!(workload) ⇒ Object



133
134
135
136
137
138
# File 'lib/core/controlplane.rb', line 133

def fetch_workload!(workload)
  workload_data = fetch_workload(workload)
  return workload_data if workload_data

  raise "Can't find workload '#{workload}', please create it with 'cpl apply-template #{workload} -a #{config.app}'."
end

#fetch_workload_deployments(workload) ⇒ Object



163
164
165
# File 'lib/core/controlplane.rb', line 163

def fetch_workload_deployments(workload)
  api.workload_deployments(workload: workload, gvc: gvc, org: org)
end

#fetch_workloads(a_gvc = gvc) ⇒ Object

workload



121
122
123
# File 'lib/core/controlplane.rb', line 121

def fetch_workloads(a_gvc = gvc)
  api.workload_list(gvc: a_gvc, org: org)
end

#fetch_workloads_by_org(a_org = org) ⇒ Object



125
126
127
# File 'lib/core/controlplane.rb', line 125

def fetch_workloads_by_org(a_org = org)
  api.workload_list_by_org(org: a_org)
end

#find_domain_for(workloads) ⇒ Object



262
263
264
265
266
267
268
269
270
# File 'lib/core/controlplane.rb', line 262

def find_domain_for(workloads)
  domains = api.list_domains(org: org)["items"]
  domains.find do |domain_data|
    route = find_domain_route(domain_data)
    next false if route.nil?

    workloads.any? { |workload| route["workloadLink"].match?(%r{/org/#{org}/gvc/#{gvc}/workload/#{workload}}) }
  end
end

#find_domain_route(data) ⇒ Object

domain



252
253
254
255
256
257
258
259
260
# File 'lib/core/controlplane.rb', line 252

def find_domain_route(data)
  port = data["spec"]["ports"].find { |current_port| current_port["number"] == 80 || current_port["number"] == 443 }
  return nil if port.nil? || port["routes"].nil?

  route = port["routes"].find { |current_route| current_route["prefix"] == "/" }
  return nil if route.nil?

  route
end

#gvc_delete(a_gvc = gvc) ⇒ Object



115
116
117
# File 'lib/core/controlplane.rb', line 115

def gvc_delete(a_gvc = gvc)
  api.gvc_delete(gvc: a_gvc, org: org)
end

#gvc_query(app_name = config.app) ⇒ Object



95
96
97
98
99
100
101
102
# File 'lib/core/controlplane.rb', line 95

def gvc_query(app_name = config.app)
  # When `match_if_app_name_starts_with` is `true`, we query for any gvc containing the name,
  # otherwise we query for a gvc with the exact name.
  op = config.should_app_start_with?(app_name) ? "~" : "="

  cmd = "cpln gvc query --org #{org} -o yaml --prop name#{op}#{app_name}"
  perform_yaml(cmd)
end

#image_build(image, dockerfile:, docker_args: [], build_args: [], push: true) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/core/controlplane.rb', line 47

def image_build(image, dockerfile:, docker_args: [], build_args: [], push: true)
  # https://docs.controlplane.com/guides/push-image#step-2
  # Might need to use `docker buildx build` if compatiblitity issues arise
  cmd = "docker build --platform=linux/amd64 -t #{image} -f #{dockerfile}"
  cmd += " --progress=plain" if ControlplaneApiDirect.trace

  cmd += " #{docker_args.join(' ')}" if docker_args.any?
  build_args.each { |build_arg| cmd += " --build-arg #{build_arg}" }
  cmd += " #{config.app_dir}"
  perform!(cmd)

  image_push(image) if push
end

#image_delete(image) ⇒ Object



61
62
63
# File 'lib/core/controlplane.rb', line 61

def image_delete(image)
  api.image_delete(org: org, image: image)
end

#image_login(org_name = config.org) ⇒ Object



65
66
67
68
69
# File 'lib/core/controlplane.rb', line 65

def (org_name = config.org)
  cmd = "cpln image docker-login --org #{org_name}"
  cmd += " > /dev/null 2>&1" if Shell.should_hide_output?
  perform!(cmd)
end

#image_pull(image) ⇒ Object



71
72
73
74
75
# File 'lib/core/controlplane.rb', line 71

def image_pull(image)
  cmd = "docker pull #{image}"
  cmd += " > /dev/null" if Shell.should_hide_output?
  perform!(cmd)
end

#image_push(image) ⇒ Object



83
84
85
86
87
# File 'lib/core/controlplane.rb', line 83

def image_push(image)
  cmd = "docker push #{image}"
  cmd += " > /dev/null" if Shell.should_hide_output?
  perform!(cmd)
end

#image_tag(old_tag, new_tag) ⇒ Object



77
78
79
80
81
# File 'lib/core/controlplane.rb', line 77

def image_tag(old_tag, new_tag)
  cmd = "docker tag #{old_tag} #{new_tag}"
  cmd += " > /dev/null" if Shell.should_hide_output?
  perform!(cmd)
end

#log_get(workload:, from:, to:) ⇒ Object



299
300
301
# File 'lib/core/controlplane.rb', line 299

def log_get(workload:, from:, to:)
  api.log_get(org: org, gvc: gvc, workload: workload, from: from, to: to)
end

#logs(workload:) ⇒ Object

logs



294
295
296
297
# File 'lib/core/controlplane.rb', line 294

def logs(workload:)
  cmd = "cpln logs '{workload=\"#{workload}\"}' --org #{org} -t -o raw --limit 200"
  perform!(cmd)
end

#parse_apply_result(result) ⇒ Object

rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity



347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
# File 'lib/core/controlplane.rb', line 347

def parse_apply_result(result) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
  items = []

  lines = result.split("\n")
  lines.each do |line|
    # The line can be in one of these formats:
    # - "Created /org/shakacode-open-source-examples/gvc/my-app-staging"
    # - "Created /org/shakacode-open-source-examples/gvc/my-app-staging/workload/redis"
    # - "Updated gvc 'tutorial-app-test-1'"
    # - "Updated workload 'redis'"
    if line.start_with?("Created")
      matches = line.match(%r{Created\s/org/[^/]+/gvc/([^/]+)($|(/([^/]+)/([^/]+)$))})&.captures
      next unless matches

      app, _, __, kind, name = matches
      if kind
        items.push({ kind: kind, name: name })
      else
        items.push({ kind: "app", name: app })
      end
    else
      matches = line.match(/Updated\s([^\s]+)\s'([^\s]+)'$/)&.captures
      next unless matches

      kind, name = matches
      kind = "app" if kind == "gvc"
      items.push({ kind: kind, name: name })
    end
  end

  items
end

#profile_create(profile, token) ⇒ Object



25
26
27
28
29
30
# File 'lib/core/controlplane.rb', line 25

def profile_create(profile, token)
  sensitive_data_pattern = /(?<=--token )(\S+)/
  cmd = "cpln profile create #{profile} --token #{token}"
  cmd += " > /dev/null" if Shell.should_hide_output?
  perform!(cmd, sensitive_data_pattern: sensitive_data_pattern)
end

#profile_delete(profile) ⇒ Object



32
33
34
35
36
# File 'lib/core/controlplane.rb', line 32

def profile_delete(profile)
  cmd = "cpln profile delete #{profile}"
  cmd += " > /dev/null" if Shell.should_hide_output?
  perform!(cmd)
end

#profile_exists?(profile) ⇒ Boolean

Returns:

  • (Boolean)


20
21
22
23
# File 'lib/core/controlplane.rb', line 20

def profile_exists?(profile)
  cmd = "cpln profile get #{profile} -o yaml"
  perform_yaml(cmd).length.positive?
end

#profile_switch(profile) ⇒ Object

profile



15
16
17
18
# File 'lib/core/controlplane.rb', line 15

def profile_switch(profile)
  ENV["CPLN_PROFILE"] = profile
  ControlplaneApiDirect.reset_api_token
end

#query_images(a_gvc = gvc, a_org = org, partial_gvc_match: nil) ⇒ Object

image



40
41
42
43
44
45
# File 'lib/core/controlplane.rb', line 40

def query_images(a_gvc = gvc, a_org = org, partial_gvc_match: nil)
  partial_gvc_match = config.should_app_start_with?(a_gvc) if partial_gvc_match.nil?
  gvc_op = partial_gvc_match ? "~" : "="

  api.query_images(org: a_org, gvc: a_gvc, gvc_op_type: gvc_op)
end

#query_workloads(workload, a_gvc = gvc, a_org = org, partial_workload_match: false, partial_gvc_match: nil) ⇒ Object



140
141
142
143
144
145
146
# File 'lib/core/controlplane.rb', line 140

def query_workloads(workload, a_gvc = gvc, a_org = org, partial_workload_match: false, partial_gvc_match: nil)
  partial_gvc_match = config.should_app_start_with?(a_gvc) if partial_gvc_match.nil?
  gvc_op = partial_gvc_match ? "~" : "="
  workload_op = partial_workload_match ? "~" : "="

  api.query_workloads(org: a_org, gvc: a_gvc, workload: workload, gvc_op_type: gvc_op, workload_op_type: workload_op)
end

#set_domain_workload(data, workload) ⇒ Object



285
286
287
288
289
290
# File 'lib/core/controlplane.rb', line 285

def set_domain_workload(data, workload)
  route = find_domain_route(data)
  route["workloadLink"] = "/org/#{org}/gvc/#{gvc}/workload/#{workload}"

  api.update_domain(org: org, domain: data["name"], data: data)
end

#set_workload_env_var(workload, container:, name:, value:) ⇒ Object



194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/core/controlplane.rb', line 194

def set_workload_env_var(workload, container:, name:, value:)
  data = fetch_workload!(workload)
  data["spec"]["containers"].each do |container_data|
    next unless container_data["name"] == container

    container_data["env"].each do |env_data|
      next unless env_data["name"] == name

      env_data["value"] = value
    end
  end

  api.update_workload(org: org, gvc: gvc, workload: workload, data: data)
end

#set_workload_suspend(workload, value) ⇒ Object



209
210
211
212
213
214
# File 'lib/core/controlplane.rb', line 209

def set_workload_suspend(workload, value)
  data = fetch_workload!(workload)
  data["spec"]["defaultOptions"]["suspend"] = value

  api.update_workload(org: org, gvc: gvc, workload: workload, data: data)
end

#workload_connect(workload, location:, container: nil, shell: nil) ⇒ Object



226
227
228
229
230
231
# File 'lib/core/controlplane.rb', line 226

def workload_connect(workload, location:, container: nil, shell: nil)
  cmd = "cpln workload connect #{workload} #{gvc_org} --location #{location}"
  cmd += " --container #{container}" if container
  cmd += " --shell #{shell}" if shell
  perform!(cmd)
end

#workload_deployment_version_ready?(version, next_version, expected_status:) ⇒ Boolean

Returns:

  • (Boolean)


167
168
169
170
171
172
173
174
# File 'lib/core/controlplane.rb', line 167

def workload_deployment_version_ready?(version, next_version, expected_status:)
  return false unless version["workload"] == next_version

  version["containers"]&.all? do |_, container|
    ready = container.dig("resources", "replicas") == container.dig("resources", "replicasReady")
    expected_status == true ? ready : !ready
  end
end

#workload_deployments_ready?(workload, expected_status:) ⇒ Boolean

Returns:

  • (Boolean)


176
177
178
179
180
181
182
183
184
185
# File 'lib/core/controlplane.rb', line 176

def workload_deployments_ready?(workload, expected_status:)
  deployments = fetch_workload_deployments(workload)["items"]
  deployments.all? do |deployment|
    next_version = deployment.dig("status", "expectedDeploymentVersion")

    deployment.dig("status", "versions")&.all? do |version|
      workload_deployment_version_ready?(version, next_version, expected_status: expected_status)
    end
  end
end

#workload_exec(workload, location:, container: nil, command: nil) ⇒ Object



233
234
235
236
237
238
# File 'lib/core/controlplane.rb', line 233

def workload_exec(workload, location:, container: nil, command: nil)
  cmd = "cpln workload exec #{workload} #{gvc_org} --location #{location}"
  cmd += " --container #{container}" if container
  cmd += " -- #{command}"
  perform!(cmd)
end

#workload_force_redeployment(workload) ⇒ Object



216
217
218
219
220
# File 'lib/core/controlplane.rb', line 216

def workload_force_redeployment(workload)
  cmd = "cpln workload force-redeployment #{workload} #{gvc_org}"
  cmd += " > /dev/null" if Shell.should_hide_output?
  perform!(cmd)
end

#workload_get_replicas(workload, location:) ⇒ Object



148
149
150
151
# File 'lib/core/controlplane.rb', line 148

def workload_get_replicas(workload, location:)
  cmd = "cpln workload get-replicas #{workload} #{gvc_org} --location #{location} -o yaml"
  perform_yaml(cmd)
end

#workload_get_replicas_safely(workload, location:) ⇒ Object



153
154
155
156
157
158
159
160
161
# File 'lib/core/controlplane.rb', line 153

def workload_get_replicas_safely(workload, location:)
  cmd = "cpln workload get-replicas #{workload} #{gvc_org} --location #{location} -o yaml"
  cmd += " 2> /dev/null" if Shell.should_hide_output?

  Shell.debug("CMD", cmd)

  result = `#{cmd}`
  $CHILD_STATUS.success? ? YAML.safe_load(result) : nil
end

#workload_set_image_ref(workload, container:, image:) ⇒ Object



187
188
189
190
191
192
# File 'lib/core/controlplane.rb', line 187

def workload_set_image_ref(workload, container:, image:)
  cmd = "cpln workload update #{workload} #{gvc_org}"
  cmd += " --set spec.containers.#{container}.image=/org/#{config.org}/image/#{image}"
  cmd += " > /dev/null" if Shell.should_hide_output?
  perform!(cmd)
end