kamal 2.6.1 → 2.8.0

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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/lib/kamal/cli/accessory.rb +15 -2
  3. data/lib/kamal/cli/app/ssl_certificates.rb +28 -0
  4. data/lib/kamal/cli/app.rb +1 -0
  5. data/lib/kamal/cli/build.rb +33 -15
  6. data/lib/kamal/cli/main.rb +7 -2
  7. data/lib/kamal/cli/port_forwarding.rb +42 -0
  8. data/lib/kamal/cli/registry.rb +16 -8
  9. data/lib/kamal/cli/templates/deploy.yml +4 -3
  10. data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +13 -1
  11. data/lib/kamal/cli/templates/secrets +1 -1
  12. data/lib/kamal/commander.rb +1 -1
  13. data/lib/kamal/commands/accessory.rb +8 -3
  14. data/lib/kamal/commands/app/execution.rb +2 -2
  15. data/lib/kamal/commands/app/proxy.rb +4 -0
  16. data/lib/kamal/commands/app.rb +4 -2
  17. data/lib/kamal/commands/base.rb +8 -0
  18. data/lib/kamal/commands/builder/base.rb +11 -1
  19. data/lib/kamal/commands/builder/local.rb +15 -2
  20. data/lib/kamal/commands/builder/pack.rb +46 -0
  21. data/lib/kamal/commands/builder/remote.rb +9 -1
  22. data/lib/kamal/commands/builder.rb +14 -2
  23. data/lib/kamal/commands/registry.rb +22 -0
  24. data/lib/kamal/configuration/accessory.rb +2 -1
  25. data/lib/kamal/configuration/builder.rb +12 -0
  26. data/lib/kamal/configuration/docs/builder.yml +13 -0
  27. data/lib/kamal/configuration/docs/proxy.yml +39 -0
  28. data/lib/kamal/configuration/proxy/boot.rb +8 -0
  29. data/lib/kamal/configuration/proxy.rb +52 -4
  30. data/lib/kamal/configuration/registry.rb +8 -0
  31. data/lib/kamal/configuration/role.rb +5 -3
  32. data/lib/kamal/configuration/validator/accessory.rb +2 -0
  33. data/lib/kamal/configuration/validator/builder.rb +2 -0
  34. data/lib/kamal/configuration/validator/proxy.rb +10 -0
  35. data/lib/kamal/configuration/validator/registry.rb +5 -3
  36. data/lib/kamal/configuration/validator/role.rb +1 -0
  37. data/lib/kamal/configuration/validator.rb +14 -0
  38. data/lib/kamal/configuration.rb +12 -3
  39. data/lib/kamal/secrets/adapters/bitwarden_secrets_manager.rb +10 -16
  40. data/lib/kamal/secrets/adapters/one_password.rb +45 -11
  41. data/lib/kamal/secrets/adapters/passbolt.rb +130 -0
  42. data/lib/kamal/version.rb +1 -1
  43. metadata +6 -2
@@ -0,0 +1,130 @@
1
+ class Kamal::Secrets::Adapters::Passbolt < Kamal::Secrets::Adapters::Base
2
+ def requires_account?
3
+ false
4
+ end
5
+
6
+ private
7
+
8
+ def login(*)
9
+ `passbolt verify`
10
+ raise RuntimeError, "Failed to login to Passbolt" unless $?.success?
11
+ end
12
+
13
+ def fetch_secrets(secrets, from:, **)
14
+ secrets = prefixed_secrets(secrets, from: from)
15
+ raise ArgumentError, "No secrets given to fetch" if secrets.empty?
16
+
17
+ secret_names = secrets.collect { |s| s.split("/").last }
18
+ folders = secrets_get_folders(secrets)
19
+
20
+ # build filter conditions for each secret with its corresponding folder
21
+ filter_conditions = []
22
+ secrets.each do |secret|
23
+ parts = secret.split("/")
24
+ secret_name = parts.last
25
+
26
+ if parts.size > 1
27
+ # get the folder path without the secret name
28
+ folder_path = parts[0..-2]
29
+
30
+ # find the most nested folder for this path
31
+ current_folder = nil
32
+ current_path = []
33
+
34
+ folder_path.each do |folder_name|
35
+ current_path << folder_name
36
+ matching_folders = folders.select { |f| get_folder_path(f, folders) == current_path.join("/") }
37
+ current_folder = matching_folders.first if matching_folders.any?
38
+ end
39
+
40
+ if current_folder
41
+ filter_conditions << "(Name == #{secret_name.shellescape.inspect} && FolderParentID == #{current_folder["id"].shellescape.inspect})"
42
+ end
43
+ else
44
+ # for root level secrets (no folders)
45
+ filter_conditions << "Name == #{secret_name.shellescape.inspect}"
46
+ end
47
+ end
48
+
49
+ filter_condition = filter_conditions.any? ? "--filter '#{filter_conditions.join(" || ")}'" : ""
50
+ items = `passbolt list resources #{filter_condition} #{folders.map { |item| "--folder #{item["id"]}" }.join(" ")} --json`
51
+ raise RuntimeError, "Could not read #{secrets} from Passbolt" unless $?.success?
52
+
53
+ items = JSON.parse(items)
54
+ found_names = items.map { |item| item["name"] }
55
+ missing_secrets = secret_names - found_names
56
+ raise RuntimeError, "Could not find the following secrets in Passbolt: #{missing_secrets.join(", ")}" if missing_secrets.any?
57
+
58
+ items.to_h { |item| [ item["name"], item["password"] ] }
59
+ end
60
+
61
+ def secrets_get_folders(secrets)
62
+ # extract all folder paths (both parent and nested)
63
+ folder_paths = secrets
64
+ .select { |s| s.include?("/") }
65
+ .map { |s| s.split("/")[0..-2] } # get all parts except the secret name
66
+ .uniq
67
+
68
+ return [] if folder_paths.empty?
69
+
70
+ all_folders = []
71
+
72
+ # first get all top-level folders
73
+ parent_folders = folder_paths.map(&:first).uniq
74
+ filter_condition = "--filter '#{parent_folders.map { |name| "Name == #{name.shellescape.inspect}" }.join(" || ")}'"
75
+ fetch_folders = `passbolt list folders #{filter_condition} --json`
76
+ raise RuntimeError, "Could not read folders from Passbolt" unless $?.success?
77
+
78
+ parent_folder_items = JSON.parse(fetch_folders)
79
+ all_folders.concat(parent_folder_items)
80
+
81
+ # get nested folders for each parent
82
+ folder_paths.each do |path|
83
+ next if path.size <= 1 # skip non-nested folders
84
+
85
+ parent = path[0]
86
+ parent_folder = parent_folder_items.find { |f| f["name"] == parent }
87
+ next unless parent_folder
88
+
89
+ # for each nested level, get the folders using the parent's ID
90
+ current_parent = parent_folder
91
+ path[1..-1].each do |folder_name|
92
+ filter_condition = "--filter 'Name == #{folder_name.shellescape.inspect} && FolderParentID == #{current_parent["id"].shellescape.inspect}'"
93
+ fetch_nested = `passbolt list folders #{filter_condition} --json`
94
+ next unless $?.success?
95
+
96
+ nested_folders = JSON.parse(fetch_nested)
97
+ break if nested_folders.empty?
98
+
99
+ all_folders.concat(nested_folders)
100
+ current_parent = nested_folders.first
101
+ end
102
+ end
103
+
104
+ # check if we found all required folders
105
+ found_paths = all_folders.map { |f| get_folder_path(f, all_folders) }
106
+ missing_paths = folder_paths.map { |path| path.join("/") } - found_paths
107
+ raise RuntimeError, "Could not find the following folders in Passbolt: #{missing_paths.join(", ")}" if missing_paths.any?
108
+
109
+ all_folders
110
+ end
111
+
112
+ def get_folder_path(folder, all_folders, path = [])
113
+ path.unshift(folder["name"])
114
+ return path.join("/") if folder["folder_parent_id"].to_s.empty?
115
+
116
+ parent = all_folders.find { |f| f["id"] == folder["folder_parent_id"] }
117
+ return path.join("/") unless parent
118
+
119
+ get_folder_path(parent, all_folders, path)
120
+ end
121
+
122
+ def check_dependencies!
123
+ raise RuntimeError, "Passbolt CLI is not installed" unless cli_installed?
124
+ end
125
+
126
+ def cli_installed?
127
+ `passbolt --version 2> /dev/null`
128
+ $?.success?
129
+ end
130
+ end
data/lib/kamal/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Kamal
2
- VERSION = "2.6.1"
2
+ VERSION = "2.8.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kamal
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.6.1
4
+ version: 2.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
@@ -220,6 +220,7 @@ files:
220
220
  - lib/kamal/cli/app/assets.rb
221
221
  - lib/kamal/cli/app/boot.rb
222
222
  - lib/kamal/cli/app/error_pages.rb
223
+ - lib/kamal/cli/app/ssl_certificates.rb
223
224
  - lib/kamal/cli/base.rb
224
225
  - lib/kamal/cli/build.rb
225
226
  - lib/kamal/cli/build/clone.rb
@@ -228,6 +229,7 @@ files:
228
229
  - lib/kamal/cli/healthcheck/poller.rb
229
230
  - lib/kamal/cli/lock.rb
230
231
  - lib/kamal/cli/main.rb
232
+ - lib/kamal/cli/port_forwarding.rb
231
233
  - lib/kamal/cli/proxy.rb
232
234
  - lib/kamal/cli/prune.rb
233
235
  - lib/kamal/cli/registry.rb
@@ -265,6 +267,7 @@ files:
265
267
  - lib/kamal/commands/builder/cloud.rb
266
268
  - lib/kamal/commands/builder/hybrid.rb
267
269
  - lib/kamal/commands/builder/local.rb
270
+ - lib/kamal/commands/builder/pack.rb
268
271
  - lib/kamal/commands/builder/remote.rb
269
272
  - lib/kamal/commands/docker.rb
270
273
  - lib/kamal/commands/hook.rb
@@ -327,6 +330,7 @@ files:
327
330
  - lib/kamal/secrets/adapters/gcp_secret_manager.rb
328
331
  - lib/kamal/secrets/adapters/last_pass.rb
329
332
  - lib/kamal/secrets/adapters/one_password.rb
333
+ - lib/kamal/secrets/adapters/passbolt.rb
330
334
  - lib/kamal/secrets/adapters/test.rb
331
335
  - lib/kamal/secrets/dotenv/inline_command_substitution.rb
332
336
  - lib/kamal/sshkit_with_ext.rb
@@ -352,7 +356,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
352
356
  - !ruby/object:Gem::Version
353
357
  version: '0'
354
358
  requirements: []
355
- rubygems_version: 3.6.7
359
+ rubygems_version: 3.6.9
356
360
  specification_version: 4
357
361
  summary: Deploy web apps in containers to servers running Docker with zero downtime.
358
362
  test_files: []