Class: Odysseus::Deployer::Executor
- Inherits:
-
Object
- Object
- Odysseus::Deployer::Executor
- Defined in:
- lib/odysseus/deployer/executor.rb
Constant Summary collapse
- WEB_ROLE =
:web
Instance Method Summary collapse
-
#boot_dependencies ⇒ Object
Boot all dependencies to their configured hosts.
-
#build(image_tag: nil, push: false, context_path: nil) ⇒ Hash
Build Docker image.
-
#build_and_deploy(image_tag: nil, context_path: nil, dry_run: false) ⇒ Hash
Build and deploy in one step.
-
#build_and_distribute(image_tag: nil, context_path: nil) ⇒ Hash
Build and distribute image based on config Uses registry if configured, otherwise pussh to hosts.
-
#build_and_push_to_registry(image_tag: nil, context_path: nil) ⇒ Hash
Build and push to registry.
-
#build_and_pussh(image_tag: nil, context_path: nil) ⇒ Hash
Build and pussh to all hosts (no registry needed).
-
#dependency_status ⇒ Object
List dependency status on all configured hosts.
-
#deploy_all(image_tag: nil, dry_run: false) ⇒ Object
Deploy all roles to their configured hosts.
-
#deploy_dependency(name:) ⇒ Object
Deploy an dependency to all its configured hosts.
-
#deploy_role(host:, role:, image_tag: nil, dry_run: false) ⇒ Object
Deploy a single role to a specific host.
-
#deploy_version(image_tag = nil) ⇒ Odysseus::DeployVersion
The identity of the deploy: version, ref and deployer.
-
#host_roles ⇒ Hash{String => Array<Symbol>}
Every configured host mapped to the roles it serves, each in config order.
-
#initialize(config_path, verbose: false) ⇒ Executor
constructor
A new instance of Executor.
-
#prune_old_images ⇒ Hash{String => Array<String>}
Delete a service's images that no host needs any more.
-
#pussh(image_tag: nil) ⇒ Hash
Push image to all configured hosts via SSH (using docker pussh).
-
#remove_dependency(name:) ⇒ Object
Remove an dependency from all its configured hosts.
-
#restart_dependency(name:) ⇒ Object
Restart an dependency on all its configured hosts.
-
#rollback_all(plan) ⇒ Hash
Roll every role on every host back to the planned version.
-
#rollback_plan(version: nil) ⇒ Odysseus::RollbackPlan
Decide what a rollback would do, without doing it.
-
#upgrade_dependency(name:) ⇒ Object
Upgrade an dependency to a new image version on all its configured hosts.
-
#uses_registry? ⇒ Boolean
Check if config uses a registry for image distribution.
-
#version_survey ⇒ Array<Odysseus::HostVersions>
What every host reports about this service's versions.
Constructor Details
#initialize(config_path, verbose: false) ⇒ Executor
Returns a new instance of Executor.
10 11 12 13 14 15 16 17 |
# File 'lib/odysseus/deployer/executor.rb', line 10 def initialize(config_path, verbose: false) @config_path = config_path @config_dir = File.dirname(config_path) parser = Odysseus::Config::Parser.new(config_path) @config = parser.parse @verbose = verbose @secrets_loader = Odysseus::Secrets::Loader.new(@config, config_dir: @config_dir) end |
Instance Method Details
#boot_dependencies ⇒ Object
Boot all dependencies to their configured hosts
284 285 286 |
# File 'lib/odysseus/deployer/executor.rb', line 284 def boot_dependencies dependency_manager.boot_all end |
#build(image_tag: nil, push: false, context_path: nil) ⇒ Hash
Build Docker image
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/odysseus/deployer/executor.rb', line 36 def build(image_tag: nil, push: false, context_path: nil) resolved = deploy_version(image_tag) context = context_path || resolve_build_context full_image = "#{@config[:image]}:#{resolved.version}" builder = build_builder if push builder.build_and_push( context_path: context, image: full_image, registry: @config[:registry] ) else builder.build(context_path: context, image: full_image) end end |
#build_and_deploy(image_tag: nil, context_path: nil, dry_run: false) ⇒ Hash
Build and deploy in one step
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/odysseus/deployer/executor.rb', line 59 def build_and_deploy(image_tag: nil, context_path: nil, dry_run: false) # First, build the image build_result = build(image_tag: image_tag, push: true, context_path: context_path) return { build: build_result, deploy: nil, success: false } unless build_result[:success] # Then deploy deploy_results = deploy_all(image_tag: image_tag, dry_run: dry_run) { build: build_result, deploy: deploy_results, success: deploy_results.values.all? { |r| r[:success] } } end |
#build_and_distribute(image_tag: nil, context_path: nil) ⇒ Hash
Build and distribute image based on config Uses registry if configured, otherwise pussh to hosts
118 119 120 121 122 123 124 |
# File 'lib/odysseus/deployer/executor.rb', line 118 def build_and_distribute(image_tag: nil, context_path: nil) if uses_registry? build_and_push_to_registry(image_tag: image_tag, context_path: context_path) else build_and_pussh(image_tag: image_tag, context_path: context_path) end end |
#build_and_push_to_registry(image_tag: nil, context_path: nil) ⇒ Hash
Build and push to registry
136 137 138 139 140 141 142 143 144 |
# File 'lib/odysseus/deployer/executor.rb', line 136 def build_and_push_to_registry(image_tag: nil, context_path: nil) build_result = build(image_tag: image_tag, push: true, context_path: context_path) { build: build_result, push: build_result[:pushed] ? { success: true } : { success: false }, success: build_result[:success] && build_result[:pushed] } end |
#build_and_pussh(image_tag: nil, context_path: nil) ⇒ Hash
Build and pussh to all hosts (no registry needed)
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/odysseus/deployer/executor.rb', line 97 def build_and_pussh(image_tag: nil, context_path: nil) # First, build the image locally build_result = build(image_tag: image_tag, push: false, context_path: context_path) return { build: build_result, pussh: nil, success: false } unless build_result[:success] # Then pussh to all hosts pussh_result = pussh(image_tag: image_tag) { build: build_result, pussh: pussh_result, success: pussh_result[:success] } end |
#dependency_status ⇒ Object
List dependency status on all configured hosts
279 280 281 |
# File 'lib/odysseus/deployer/executor.rb', line 279 def dependency_status dependency_manager.status end |
#deploy_all(image_tag: nil, dry_run: false) ⇒ Object
Deploy all roles to their configured hosts
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/odysseus/deployer/executor.rb', line 149 def deploy_all(image_tag: nil, dry_run: false) results = {} @config[:servers].each do |role, role_config| hosts = resolve_hosts(role_config) hosts.each do |host| puts "\n=== Deploying #{role} to #{host} ===" results["#{role}@#{host}"] = deploy_role(host: host, image_tag: image_tag, dry_run: dry_run, role: role) end end prune_old_images unless dry_run results end |
#deploy_dependency(name:) ⇒ Object
Deploy an dependency to all its configured hosts
256 257 258 |
# File 'lib/odysseus/deployer/executor.rb', line 256 def deploy_dependency(name:) dependency_manager.deploy(name: name) end |
#deploy_role(host:, role:, image_tag: nil, dry_run: false) ⇒ Object
Deploy a single role to a specific host
240 241 242 243 244 245 246 247 248 249 250 251 252 |
# File 'lib/odysseus/deployer/executor.rb', line 240 def deploy_role(host:, role:, image_tag: nil, dry_run: false) resolved = deploy_version(image_tag) if dry_run puts "Dry run - would deploy #{@config[:image]}:#{resolved.version} to #{host}" puts "Service: #{@config[:service]}" puts "Role: #{role}" puts "Proxy hosts: #{@config[:proxy][:hosts].join(', ')}" if role == WEB_ROLE return { success: true, dry_run: true } end run_deploy(host: host, role: role, resolved: resolved) end |
#deploy_version(image_tag = nil) ⇒ Odysseus::DeployVersion
The identity of the deploy: version, ref and deployer.
Resolved once per tag so a multi-role, multi-host deploy cannot end up with two versions, and so the git commands run once rather than per host.
26 27 28 29 |
# File 'lib/odysseus/deployer/executor.rb', line 26 def deploy_version(image_tag = nil) @deploy_versions ||= {} @deploy_versions[image_tag] ||= version_resolver.resolve(image_tag: image_tag) end |
#host_roles ⇒ Hash{String => Array<Symbol>}
Every configured host mapped to the roles it serves, each in config order. A host serving two roles (e.g. web and cron on the same box) gets both, in the order its roles are declared in deploy.yml — the order #version_survey depends on to know which role's containers to look for first.
Public API, not just an internal helper: the CLI asks this exact
question too — which hosts does this config target, and which roles
on each — to visit every host once regardless of how many roles it
serves. odysseus doctor depends on it.
300 301 302 303 304 305 306 307 308 |
# File 'lib/odysseus/deployer/executor.rb', line 300 def host_roles roles_by_host = {} @config[:servers].each do |role, role_config| resolve_hosts(role_config).each { |host| (roles_by_host[host] ||= []) << role } end roles_by_host end |
#prune_old_images ⇒ Hash{String => Array<String>}
Delete a service's images that no host needs any more.
231 232 233 |
# File 'lib/odysseus/deployer/executor.rb', line 231 def prune_old_images retention_sweeper.sweep(host_roles) end |
#pussh(image_tag: nil) ⇒ Hash
Push image to all configured hosts via SSH (using docker pussh)
78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/odysseus/deployer/executor.rb', line 78 def pussh(image_tag: nil) resolved = deploy_version(image_tag) full_image = "#{@config[:image]}:#{resolved.version}" hosts = collect_all_hosts return { success: false, error: 'No hosts configured' } if hosts.empty? builder = build_builder builder.pussh_to_hosts( image: full_image, hosts: hosts, user: @config[:ssh][:user] ) end |
#remove_dependency(name:) ⇒ Object
Remove an dependency from all its configured hosts
262 263 264 |
# File 'lib/odysseus/deployer/executor.rb', line 262 def remove_dependency(name:) dependency_manager.remove(name: name) end |
#restart_dependency(name:) ⇒ Object
Restart an dependency on all its configured hosts
268 269 270 |
# File 'lib/odysseus/deployer/executor.rb', line 268 def restart_dependency(name:) dependency_manager.restart(name: name) end |
#rollback_all(plan) ⇒ Hash
Roll every role on every host back to the planned version.
Reuses the deploy path unchanged, so health gating, proxy handling and zero-downtime behaviour are shared with deploy rather than reimplemented. Sequential, and inheriting deploy's partial-failure semantics: the plan's pre-flight rules out the common cause of a half-rolled-back fleet — a missing image — but does not make the roll atomic.
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'lib/odysseus/deployer/executor.rb', line 209 def rollback_all(plan) resolved = Odysseus::DeployVersion.new( version: plan.version, ref: plan.ref, deployer: version_resolver.deployer ) results = {} @config[:servers].each do |role, role_config| resolve_hosts(role_config).each do |host| puts "\n=== Rolling back #{role} on #{host} to #{plan.version} ===" results["#{role}@#{host}"] = run_deploy( host: host, role: role, resolved: resolved, kind: 'rolled-back', from: plan.from_for(host) ) end end results end |
#rollback_plan(version: nil) ⇒ Odysseus::RollbackPlan
Decide what a rollback would do, without doing it.
Surveys the fleet and returns the plan, or raises RollbackError with a message naming the hosts at fault. Separate from rollback_all so the caller can show the target — and any approximate-ordering warning — before anything is touched, and so the survey runs once.
194 195 196 |
# File 'lib/odysseus/deployer/executor.rb', line 194 def rollback_plan(version: nil) Odysseus::RollbackPlanner.new(version_survey).plan(version: version) end |
#upgrade_dependency(name:) ⇒ Object
Upgrade an dependency to a new image version on all its configured hosts
274 275 276 |
# File 'lib/odysseus/deployer/executor.rb', line 274 def upgrade_dependency(name:) dependency_manager.upgrade(name: name) end |
#uses_registry? ⇒ Boolean
Check if config uses a registry for image distribution
128 129 130 |
# File 'lib/odysseus/deployer/executor.rb', line 128 def uses_registry? @config[:registry] && @config[:registry][:server] end |
#version_survey ⇒ Array<Odysseus::HostVersions>
What every host reports about this service's versions.
One entry per unique host across all roles, so a host serving two roles is surveyed once. Connections are opened and closed per host.
171 172 173 174 175 176 177 178 179 180 181 182 183 |
# File 'lib/odysseus/deployer/executor.rb', line 171 def version_survey host_roles.map do |host, roles| ssh = connect_to_server(host) begin Odysseus::HostVersions.read( host: host, ssh: ssh, service: @config[:service], image: @config[:image], roles: roles ) ensure ssh.close end end end |