cpl 2.1.0 → 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 468f47cb0e1cf3cdb710b885949188c68f40fd74ad4236379a5a6c6fd55739c1
4
- data.tar.gz: fc83d4ef5ee3ded08d52ca5f2df92e5ee261df6fcefd27f673452d0ba265a650
3
+ metadata.gz: 81caf0d255f4e34e0992abfe2f757f9bf8f248e4fedec251c2f9b42041a6f778
4
+ data.tar.gz: b82433c165d83069894c7bd8b05d24a7b1d06dacd7065577a13c161778ff8833
5
5
  SHA512:
6
- metadata.gz: dac1051680d2b3c69473636a9babd19b8799ea6e2eaadbb26582836641f7a73e843e4a26c32f41206edb0a6b21ab89b9b62cc08087360d75a562c3b6bb40188d
7
- data.tar.gz: 52ca1f46ff0d459f450e1a1bd82b6efce780e9b6480ebbda955722eef4bea3a98d71cdaff076691b4cfe93e77c42285f358bfb4ba9adf857c961108302a4f5d6
6
+ metadata.gz: a9975e154888a11ea748ebd384e6349727510179d8e90b8cec7ee569a4b9bf3ed43c3200405796994a55de97cd63f228ca37c7bfefaf40875d815dd185cdeac4
7
+ data.tar.gz: '04910e7adbf92244805ccb535fbf3f8452df999b95b101a6f373eb8a004b251f3a1c16d63eab460542a03169e15b1f1e597737f194e59c1594c6700f132fb578'
data/CHANGELOG.md CHANGED
@@ -16,6 +16,29 @@ _Please add entries here for your pull requests that have not yet been released.
16
16
 
17
17
  ### Fixed
18
18
 
19
+ - Fixed issue where latest image may be incorrect. [PR 201](https://github.com/shakacode/control-plane-flow/pull/201) by [Rafael Gomes](https://github.com/rafaelgomesxyz).
20
+ - Fixed issue where `build-image` command hangs forever waiting for image to be available. [PR 201](https://github.com/shakacode/control-plane-flow/pull/201) by [Rafael Gomes](https://github.com/rafaelgomesxyz).
21
+
22
+ ## [2.2.0] - 2024-06-07
23
+
24
+ ### Fixed
25
+
26
+ - Fixed issue where `ps:wait` command hangs forever if workloads are suspended. [PR 198](https://github.com/shakacode/control-plane-flow/pull/198) by [Rafael Gomes](https://github.com/rafaelgomesxyz).
27
+
28
+ ### Added
29
+
30
+ - Added a timeout for `run` jobs (6 hours by default, but configurable through `runner_job_timeout` in `controlplane.yml`). [PR 194](https://github.com/shakacode/control-plane-flow/pull/194) by [Rafael Gomes](https://github.com/rafaelgomesxyz).
31
+
32
+ ### Changed
33
+
34
+ - `run` command now overrides the `--image`, `--cpu`, and `--memory` for each job separately, which completely removes any race conditions when running simultaneous jobs with different overrides. [PR 182](https://github.com/shakacode/control-plane-flow/pull/182) by [Rafael Gomes](https://github.com/rafaelgomesxyz).
35
+ - `run` jobs now use a CPU size of 1 (1 core) and a memory size of 2Gi (2 gibibytes) by default (configurable through `runner_job_default_cpu` and `runner_job_default_memory` in `controlplane.yml`). [PR 182](https://github.com/shakacode/control-plane-flow/pull/182) by [Rafael Gomes](https://github.com/rafaelgomesxyz).
36
+ - `run` command now keeps ENV values synced between original and runner workloads. [PR 196](https://github.com/shakacode/control-plane-flow/pull/196) by [Rafael Gomes](https://github.com/rafaelgomesxyz).
37
+
38
+ ## [2.1.0] - 2024-05-27
39
+
40
+ ### Fixed
41
+
19
42
  - Fixed issue where release script was not running from the app image. [PR 183](https://github.com/shakacode/control-plane-flow/pull/183) by [Rafael Gomes](https://github.com/rafaelgomesxyz).
20
43
  - Fixed issue where deprecated options were not being warned. [PR 183](https://github.com/shakacode/control-plane-flow/pull/183) by [Rafael Gomes](https://github.com/rafaelgomesxyz).
21
44
 
@@ -214,7 +237,9 @@ _Please add entries here for your pull requests that have not yet been released.
214
237
 
215
238
  - Initial release
216
239
 
217
- [Unreleased]: https://github.com/shakacode/control-plane-flow/compare/v2.0.2...HEAD
240
+ [Unreleased]: https://github.com/shakacode/control-plane-flow/compare/v2.2.0...HEAD
241
+ [2.2.0]: https://github.com/shakacode/control-plane-flow/compare/v2.1.0...v2.2.0
242
+ [2.1.0]: https://github.com/shakacode/control-plane-flow/compare/v2.0.2...v2.1.0
218
243
  [2.0.2]: https://github.com/shakacode/control-plane-flow/compare/v2.0.1...v2.0.2
219
244
  [2.0.1]: https://github.com/shakacode/control-plane-flow/compare/v2.0.0...v2.0.1
220
245
  [2.0.0]: https://github.com/shakacode/control-plane-flow/compare/v1.4.0...v2.0.0
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cpl (2.1.0)
4
+ cpl (2.2.1)
5
5
  debug (~> 1.7.1)
6
6
  dotenv (~> 2.8.1)
7
7
  jwt (~> 2.8.1)
@@ -52,7 +52,7 @@ GEM
52
52
  rdoc (6.7.0)
53
53
  psych (>= 4.0.0)
54
54
  regexp_parser (2.9.0)
55
- reline (0.5.7)
55
+ reline (0.5.9)
56
56
  io-console (~> 0.5)
57
57
  rexml (3.2.6)
58
58
  rspec (3.12.0)
@@ -96,7 +96,7 @@ GEM
96
96
  simplecov_json_formatter (~> 0.1)
97
97
  simplecov-html (0.12.3)
98
98
  simplecov_json_formatter (0.1.4)
99
- stringio (3.1.0)
99
+ stringio (3.1.1)
100
100
  thor (1.2.2)
101
101
  timecop (0.9.8)
102
102
  unicode-display_width (2.5.0)
data/README.md CHANGED
@@ -249,6 +249,18 @@ aliases:
249
249
  # when running `cpl run`.
250
250
  fix_terminal_size: true
251
251
 
252
+ # Sets a default CPU size for `cpl run` jobs (can be overridden per job through `--cpu`).
253
+ # If not specified, defaults to "1" (1 core).
254
+ runner_job_default_cpu: "2"
255
+
256
+ # Sets a default memory size for `cpl run` jobs (can be overridden per job through `--memory`).
257
+ # If not specified, defaults to "2Gi" (2 gibibytes).
258
+ runner_job_default_memory: "4Gi"
259
+
260
+ # Sets the maximum number of seconds that `cpl run` jobs can execute before being stopped.
261
+ # If not specified, defaults to 21600 (6 hours).
262
+ runner_job_timeout: 1000
263
+
252
264
  # Apps with a deployed image created before this amount of days will be listed for deletion
253
265
  # when running the command `cpl cleanup-stale-apps`.
254
266
  stale_app_image_deployed_days: 5
data/docs/commands.md CHANGED
@@ -371,12 +371,17 @@ cpl ps:swait -a $APP_NAME -w $WORKLOAD_NAME
371
371
  - - log async fetching for non-interactive mode
372
372
  - The Dockerfile entrypoint is used as the command by default, which assumes `exec "${@}"` to be present,
373
373
  and the args ["bash", "-c", cmd_to_run] are passed
374
- - The entrypoint can be overriden through `--entrypoint`, which must be a single command or a script path that exists in the container,
374
+ - The entrypoint can be overridden through `--entrypoint`, which must be a single command or a script path that exists in the container,
375
375
  and the args ["bash", "-c", cmd_to_run] are passed,
376
376
  unless the entrypoint is `bash`, in which case the args ["-c", cmd_to_run] are passed
377
377
  - Providing `--entrypoint none` sets the entrypoint to `bash` by default
378
378
  - If `fix_terminal_size` is `true` in the `.controlplane/controlplane.yml` file,
379
- the remote terminal size will be fixed to match the local terminal size (may also be overriden through `--terminal-size`)
379
+ the remote terminal size will be fixed to match the local terminal size (may also be overridden through `--terminal-size`)
380
+ - By default, all jobs use a CPU size of 1 (1 core) and a memory size of 2Gi (2 gibibytes)
381
+ (can be configured through `runner_job_default_cpu` and `runner_job_default_memory` in `controlplane.yml`,
382
+ and also overridden per job through `--cpu` and `--memory`)
383
+ - By default, the job is stopped if it takes longer than 6 hours to finish
384
+ (can be configured though `runner_job_timeout` in `controlplane.yml`)
380
385
 
381
386
  ```sh
382
387
  # Opens shell (bash by default).
@@ -84,6 +84,18 @@ aliases:
84
84
  # when running `cpl run`.
85
85
  fix_terminal_size: true
86
86
 
87
+ # Sets a default CPU size for `cpl run` jobs (can be overridden per job through `--cpu`).
88
+ # If not specified, defaults to "1" (1 core).
89
+ runner_job_default_cpu: "2"
90
+
91
+ # Sets a default memory size for `cpl run` jobs (can be overridden per job through `--memory`).
92
+ # If not specified, defaults to "2Gi" (2 gibibytes).
93
+ runner_job_default_memory: "4Gi"
94
+
95
+ # Sets the maximum number of seconds that `cpl run` jobs can execute before being stopped.
96
+ # If not specified, defaults to 21600 (6 hours).
97
+ runner_job_timeout: 1000
98
+
87
99
  # Apps with a deployed image created before this amount of days will be listed for deletion
88
100
  # when running the command `cpl cleanup-stale-apps`.
89
101
  stale_app_image_deployed_days: 5
@@ -41,7 +41,8 @@ module Command
41
41
  progress.puts("\nPushed image to '/org/#{config.org}/image/#{image_name}'.\n\n")
42
42
 
43
43
  step("Waiting for image to be available", retry_on_failure: true) do
44
- image_name == cp.latest_image(refresh: true)
44
+ images = cp.query_images["items"]
45
+ images.find { |image| image["name"] == image_name }
45
46
  end
46
47
  end
47
48
  end
@@ -22,13 +22,17 @@ module Command
22
22
  ```
23
23
  EX
24
24
 
25
- def call
25
+ def call # rubocop:disable Metrics/MethodLength
26
26
  @workloads = [config.options[:workload]] if config.options[:workload]
27
27
  @workloads ||= config[:app_workloads] + config[:additional_workloads]
28
28
 
29
29
  @workloads.reverse_each do |workload|
30
- step("Waiting for workload '#{workload}' to be ready", retry_on_failure: true) do
31
- cp.workload_deployments_ready?(workload, location: config.location, expected_status: true)
30
+ if cp.workload_suspended?(workload)
31
+ progress.puts("Workload '#{workload}' is suspended. Skipping...")
32
+ else
33
+ step("Waiting for workload '#{workload}' to be ready", retry_on_failure: true) do
34
+ cp.workload_deployments_ready?(workload, location: config.location, expected_status: true)
35
+ end
32
36
  end
33
37
  end
34
38
  end
data/lib/command/run.rb CHANGED
@@ -36,12 +36,17 @@ module Command
36
36
  - - log async fetching for non-interactive mode
37
37
  - The Dockerfile entrypoint is used as the command by default, which assumes `exec "${@}"` to be present,
38
38
  and the args ["bash", "-c", cmd_to_run] are passed
39
- - The entrypoint can be overriden through `--entrypoint`, which must be a single command or a script path that exists in the container,
39
+ - The entrypoint can be overridden through `--entrypoint`, which must be a single command or a script path that exists in the container,
40
40
  and the args ["bash", "-c", cmd_to_run] are passed,
41
41
  unless the entrypoint is `bash`, in which case the args ["-c", cmd_to_run] are passed
42
42
  - Providing `--entrypoint none` sets the entrypoint to `bash` by default
43
43
  - If `fix_terminal_size` is `true` in the `.controlplane/controlplane.yml` file,
44
- the remote terminal size will be fixed to match the local terminal size (may also be overriden through `--terminal-size`)
44
+ the remote terminal size will be fixed to match the local terminal size (may also be overridden through `--terminal-size`)
45
+ - By default, all jobs use a CPU size of 1 (1 core) and a memory size of 2Gi (2 gibibytes)
46
+ (can be configured through `runner_job_default_cpu` and `runner_job_default_memory` in `controlplane.yml`,
47
+ and also overridden per job through `--cpu` and `--memory`)
48
+ - By default, the job is stopped if it takes longer than 6 hours to finish
49
+ (can be configured though `runner_job_timeout` in `controlplane.yml`)
45
50
  DESC
46
51
  EXAMPLES = <<~EX
47
52
  ```sh
@@ -84,12 +89,17 @@ module Command
84
89
  ```
85
90
  EX
86
91
 
92
+ DEFAULT_JOB_CPU = "1"
93
+ DEFAULT_JOB_MEMORY = "2Gi"
94
+ DEFAULT_JOB_TIMEOUT = 21_600 # 6 hours
95
+ DEFAULT_JOB_HISTORY_LIMIT = 10
87
96
  MAGIC_END = "---cpl run command finished---"
88
97
 
89
98
  attr_reader :interactive, :detached, :location, :original_workload, :runner_workload,
99
+ :default_image, :default_cpu, :default_memory, :job_timeout, :job_history_limit,
90
100
  :container, :expected_deployed_version, :job, :replica, :command
91
101
 
92
- def call # rubocop:disable Metrics/MethodLength
102
+ def call # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
93
103
  @interactive = config.options[:interactive] || interactive_command?
94
104
  @detached = config.options[:detached]
95
105
  @log_method = config.options[:log_method]
@@ -97,6 +107,11 @@ module Command
97
107
  @location = config.location
98
108
  @original_workload = config.options[:workload] || config[:one_off_workload]
99
109
  @runner_workload = "#{original_workload}-runner"
110
+ @default_image = "#{config.app}:#{Controlplane::NO_IMAGE_AVAILABLE}"
111
+ @default_cpu = config.current[:runner_job_default_cpu] || DEFAULT_JOB_CPU
112
+ @default_memory = config.current[:runner_job_default_memory] || DEFAULT_JOB_MEMORY
113
+ @job_timeout = config.current[:runner_job_timeout] || DEFAULT_JOB_TIMEOUT
114
+ @job_history_limit = DEFAULT_JOB_HISTORY_LIMIT
100
115
 
101
116
  unless interactive
102
117
  @internal_sigint = false
@@ -110,12 +125,10 @@ module Command
110
125
  end
111
126
  end
112
127
 
113
- if cp.fetch_workload(runner_workload).nil?
114
- create_runner_workload
115
- wait_for_runner_workload_create
116
- end
128
+ create_runner_workload if cp.fetch_workload(runner_workload).nil?
129
+ wait_for_runner_workload_deploy
117
130
  update_runner_workload
118
- wait_for_runner_workload_update
131
+ wait_for_runner_workload_update if expected_deployed_version
119
132
 
120
133
  start_job
121
134
  wait_for_replica_for_job
@@ -154,6 +167,11 @@ module Command
154
167
  container_spec.delete("livenessProbe")
155
168
  container_spec.delete("readinessProbe")
156
169
 
170
+ # Set image, CPU, and memory to default values
171
+ container_spec["image"] = default_image
172
+ container_spec["cpu"] = default_cpu
173
+ container_spec["memory"] = default_memory
174
+
157
175
  # Ensure cron workload won't run per schedule
158
176
  spec["defaultOptions"]["suspend"] = true
159
177
 
@@ -163,9 +181,14 @@ module Command
163
181
 
164
182
  # Set cron job props
165
183
  spec["type"] = "cron"
184
+ spec["job"] = {
185
+ # Next job set to run on January 1st, 2029
186
+ "schedule" => "0 0 1 1 1",
166
187
 
167
- # Next job set to run on January 1st, 2029
168
- spec["job"] = { "schedule" => "0 0 1 1 1", "restartPolicy" => "Never" }
188
+ "restartPolicy" => "Never",
189
+ "activeDeadlineSeconds" => job_timeout,
190
+ "historyLimit" => job_history_limit
191
+ }
169
192
 
170
193
  # Create runner workload
171
194
  cp.apply_hash("kind" => "workload", "name" => runner_workload, "spec" => spec)
@@ -173,46 +196,60 @@ module Command
173
196
  end
174
197
 
175
198
  def update_runner_workload # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
176
- step("Updating runner workload '#{runner_workload}' based on '#{original_workload}'") do # rubocop:disable Metrics/BlockLength
177
- @expected_deployed_version = cp.cron_workload_deployed_version(runner_workload)
178
- should_update = false
199
+ should_update = false
200
+ spec = nil
179
201
 
202
+ step("Checking if runner workload '#{runner_workload}' needs to be updated") do # rubocop:disable Metrics/BlockLength
180
203
  _, original_container_spec = base_workload_specs(original_workload)
181
204
  spec, container_spec = base_workload_specs(runner_workload)
182
205
 
183
- # Override image if specified
184
- image = config.options[:image]
185
- image_link = if image
186
- image = cp.latest_image if image == "latest"
187
- "/org/#{config.org}/image/#{image}"
188
- else
189
- original_container_spec["image"]
190
- end
191
- if container_spec["image"] != image_link
192
- container_spec["image"] = image_link
206
+ # Keep ENV synced between original and runner workloads
207
+ original_env_str = original_container_spec["env"]&.sort_by { |env| env["name"] }.to_s
208
+ env_str = container_spec["env"]&.sort_by { |env| env["name"] }.to_s
209
+ if original_env_str != env_str
210
+ container_spec["env"] = original_container_spec["env"]
211
+ should_update = true
212
+ end
213
+
214
+ if container_spec["image"] != default_image
215
+ container_spec["image"] = default_image
193
216
  should_update = true
194
217
  end
195
218
 
196
- # Container overrides
197
- if config.options[:cpu] && container_spec["cpu"] != config.options[:cpu]
198
- container_spec["cpu"] = config.options[:cpu]
219
+ if container_spec["cpu"] != default_cpu
220
+ container_spec["cpu"] = default_cpu
199
221
  should_update = true
200
222
  end
201
- if config.options[:memory] && container_spec["memory"] != config.options[:memory]
202
- container_spec["memory"] = config.options[:memory]
223
+
224
+ if container_spec["memory"] != default_memory
225
+ container_spec["memory"] = default_memory
203
226
  should_update = true
204
227
  end
205
228
 
206
- next true unless should_update
229
+ if spec["job"]["activeDeadlineSeconds"] != job_timeout
230
+ spec["job"]["activeDeadlineSeconds"] = job_timeout
231
+ should_update = true
232
+ end
207
233
 
234
+ if spec["job"]["historyLimit"] != job_history_limit
235
+ spec["job"]["historyLimit"] = job_history_limit
236
+ should_update = true
237
+ end
238
+
239
+ true
240
+ end
241
+
242
+ return unless should_update
243
+
244
+ step("Updating runner workload '#{runner_workload}'") do
208
245
  # Update runner workload
209
- @expected_deployed_version += 1
246
+ @expected_deployed_version = cp.cron_workload_deployed_version(runner_workload) + 1
210
247
  cp.apply_hash("kind" => "workload", "name" => runner_workload, "spec" => spec)
211
248
  end
212
249
  end
213
250
 
214
- def wait_for_runner_workload_create
215
- step("Waiting for runner workload '#{runner_workload}' to be created", retry_on_failure: true) do
251
+ def wait_for_runner_workload_deploy
252
+ step("Waiting for runner workload '#{runner_workload}' to be deployed", retry_on_failure: true) do
216
253
  !cp.cron_workload_deployed_version(runner_workload).nil?
217
254
  end
218
255
  end
@@ -302,12 +339,14 @@ module Command
302
339
  def base_workload_specs(workload)
303
340
  spec = cp.fetch_workload!(workload).fetch("spec")
304
341
  container_spec = spec["containers"].detect { _1["name"] == original_workload } || spec["containers"].first
305
- @container = container_spec["name"]
306
342
 
307
343
  [spec, container_spec]
308
344
  end
309
345
 
310
346
  def build_job_start_yaml # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
347
+ _, original_container_spec = base_workload_specs(original_workload)
348
+ @container = original_container_spec["name"]
349
+
311
350
  job_start_hash = { "name" => container }
312
351
 
313
352
  if config.options[:use_local_token]
@@ -335,6 +374,18 @@ module Command
335
374
  job_start_hash["args"].push('eval "$CPL_RUNNER_SCRIPT"')
336
375
  end
337
376
 
377
+ image = config.options[:image]
378
+ image_link = if image
379
+ image = cp.latest_image if image == "latest"
380
+ "/org/#{config.org}/image/#{image}"
381
+ else
382
+ original_container_spec["image"]
383
+ end
384
+
385
+ job_start_hash["image"] = image_link
386
+ job_start_hash["cpu"] = config.options[:cpu] if config.options[:cpu]
387
+ job_start_hash["memory"] = config.options[:memory] if config.options[:memory]
388
+
338
389
  job_start_hash.to_yaml
339
390
  end
340
391
 
@@ -434,6 +485,8 @@ module Command
434
485
  end
435
486
 
436
487
  def print_detached_commands
488
+ return unless replica
489
+
437
490
  app_workload_replica_config = app_workload_replica_args.join(" ")
438
491
  progress.puts(
439
492
  "\n\n" \
@@ -451,7 +504,7 @@ module Command
451
504
  Shell.debug("JOB STATUS", status)
452
505
 
453
506
  case status
454
- when "active"
507
+ when "active", "pending"
455
508
  sleep 1
456
509
  when "successful"
457
510
  break ExitCode::SUCCESS
@@ -39,9 +39,8 @@ class Controlplane # rubocop:disable Metrics/ClassLength
39
39
 
40
40
  # image
41
41
 
42
- def latest_image(a_gvc = gvc, a_org = org, refresh: false)
42
+ def latest_image(a_gvc = gvc, a_org = org)
43
43
  @latest_image ||= {}
44
- @latest_image[a_gvc] = nil if refresh
45
44
  @latest_image[a_gvc] ||=
46
45
  begin
47
46
  items = query_images(a_gvc, a_org)["items"]
@@ -69,7 +68,7 @@ class Controlplane # rubocop:disable Metrics/ClassLength
69
68
  if matching_items.empty?
70
69
  name_only ? "#{app_name}:#{NO_IMAGE_AVAILABLE}" : nil
71
70
  else
72
- latest_item = matching_items.max_by { |item| extract_image_number(item["name"]) }
71
+ latest_item = matching_items.max_by { |item| DateTime.parse(item["created"]) }
73
72
  name_only ? latest_item["name"] : latest_item
74
73
  end
75
74
  end
@@ -256,6 +255,11 @@ class Controlplane # rubocop:disable Metrics/ClassLength
256
255
  api.update_workload(org: org, gvc: gvc, workload: workload, data: data)
257
256
  end
258
257
 
258
+ def workload_suspended?(workload)
259
+ details = fetch_workload!(workload)
260
+ details["spec"]["defaultOptions"]["suspend"]
261
+ end
262
+
259
263
  def workload_force_redeployment(workload)
260
264
  cmd = "cpln workload force-redeployment #{workload} #{gvc_org}"
261
265
  perform!(cmd)
data/lib/cpl/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cpl
4
- VERSION = "2.1.0"
4
+ VERSION = "2.2.1"
5
5
  MIN_CPLN_VERSION = "2.0.1"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cpl
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Gordon
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2024-05-27 00:00:00.000000000 Z
12
+ date: 2024-06-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: debug