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.
- checksums.yaml +4 -4
 - data/lib/kamal/cli/accessory.rb +15 -2
 - data/lib/kamal/cli/app/ssl_certificates.rb +28 -0
 - data/lib/kamal/cli/app.rb +1 -0
 - data/lib/kamal/cli/build.rb +33 -15
 - data/lib/kamal/cli/main.rb +7 -2
 - data/lib/kamal/cli/port_forwarding.rb +42 -0
 - data/lib/kamal/cli/registry.rb +16 -8
 - data/lib/kamal/cli/templates/deploy.yml +4 -3
 - data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +13 -1
 - data/lib/kamal/cli/templates/secrets +1 -1
 - data/lib/kamal/commander.rb +1 -1
 - data/lib/kamal/commands/accessory.rb +8 -3
 - data/lib/kamal/commands/app/execution.rb +2 -2
 - data/lib/kamal/commands/app/proxy.rb +4 -0
 - data/lib/kamal/commands/app.rb +4 -2
 - data/lib/kamal/commands/base.rb +8 -0
 - data/lib/kamal/commands/builder/base.rb +11 -1
 - data/lib/kamal/commands/builder/local.rb +15 -2
 - data/lib/kamal/commands/builder/pack.rb +46 -0
 - data/lib/kamal/commands/builder/remote.rb +9 -1
 - data/lib/kamal/commands/builder.rb +14 -2
 - data/lib/kamal/commands/registry.rb +22 -0
 - data/lib/kamal/configuration/accessory.rb +2 -1
 - data/lib/kamal/configuration/builder.rb +12 -0
 - data/lib/kamal/configuration/docs/builder.yml +13 -0
 - data/lib/kamal/configuration/docs/proxy.yml +39 -0
 - data/lib/kamal/configuration/proxy/boot.rb +8 -0
 - data/lib/kamal/configuration/proxy.rb +52 -4
 - data/lib/kamal/configuration/registry.rb +8 -0
 - data/lib/kamal/configuration/role.rb +5 -3
 - data/lib/kamal/configuration/validator/accessory.rb +2 -0
 - data/lib/kamal/configuration/validator/builder.rb +2 -0
 - data/lib/kamal/configuration/validator/proxy.rb +10 -0
 - data/lib/kamal/configuration/validator/registry.rb +5 -3
 - data/lib/kamal/configuration/validator/role.rb +1 -0
 - data/lib/kamal/configuration/validator.rb +14 -0
 - data/lib/kamal/configuration.rb +12 -3
 - data/lib/kamal/secrets/adapters/bitwarden_secrets_manager.rb +10 -16
 - data/lib/kamal/secrets/adapters/one_password.rb +45 -11
 - data/lib/kamal/secrets/adapters/passbolt.rb +130 -0
 - data/lib/kamal/version.rb +1 -1
 - 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
    
    
    
        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. 
     | 
| 
      
 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. 
     | 
| 
      
 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: []
         
     |