class Deployer
Constants
- CONFIG_DEFAULTS
- CONFIG_LOCATIONS
Attributes
branch[R]
commit[R]
default_branch[R]
default_context[R]
image_tag[R]
images[R]
production_context[R]
registry[R]
repository[R]
root[R]
spec_dir[RW]
ssh_host[R]
Public Class Methods
docker()
click to toggle source
# File lib/deployer.rb, line 23 def self.docker @docker_path ||= File.which('docker') || File.which('podman') end
new(root = nil)
click to toggle source
# File lib/deployer.rb, line 31 def initialize(root = nil) @images = [] @specs = {} @root = root || Dir.pwd load_config self.branch = default_branch end
podman?()
click to toggle source
# File lib/deployer.rb, line 27 def self.podman? docker.include?('podman') end
Public Instance Methods
branch=(branch)
click to toggle source
# File lib/deployer.rb, line 40 def branch=(branch) return unless branch @branch = branch.dup @branch.delete_prefix!('g') if @branch.match(/^g\h{8}$/) @commit = git.object(@branch).sha @image_tag = @commit[0, 8] images.each do |image| image.commit = commit image.tag = image_tag end end
deploy!(context)
click to toggle source
# File lib/deployer.rb, line 91 def deploy!(context) context = (context || default_context).to_s specs = deploy_specs(context).presence or raise "No kubernetes specs to deploy" stdout, stderr, _success = kubectl(context, 'apply -f -', YAML.dump_stream(*specs)) puts stdout if stdout.present? puts stderr if stderr.present? end
deploy_specs(context = nil)
click to toggle source
# File lib/deployer.rb, line 99 def deploy_specs(context = nil) dspecs = [] specs(context).deep_dup.each do |spec| containers = Array(spec.dig('spec', 'template', 'spec', 'containers')) + # deployments/statefulsets Array(spec.dig('spec', 'jobTemplate', 'spec', 'template', 'spec', 'containers')) # cronjobs containers.each do |container| image = images.detect { |image| image.remote_repo == container['image'] } if image container['image'] = image.remote_image dspecs << spec unless dspecs.include?(spec) elsif !container['image'].include?(':') raise "Unknown image #{container['image']}" end end end dspecs end
kubectl(context, cmd, data = nil)
click to toggle source
# File lib/deployer.rb, line 129 def kubectl(context, cmd, data = nil) cmd = "kubectl --context #{context} #{cmd}" if ssh_host.blank? stdout, stderr, cmd_status = Open3.capture3(cmd, stdin_data: data) [ stdout, stderr, cmd_status.success? ] else require 'net/ssh' exit_code = -1 stdout = String.new stderr = String.new ssh = Net::SSH.start(ssh_host) ssh.open_channel do |channel| channel.exec(cmd) do |_ch, success| success or raise "FAILED: couldn't execute command on #{ssh_host}: #{cmd.inspect}" channel.on_data { |_ch, in_data| stdout << in_data } channel.on_extended_data { |_ch, _type, in_data| stderr << in_data } channel.on_request('exit-status') { |_ch, in_data| exit_code = in_data.read_long } channel.send_data(data) if data channel.eof! end end ssh.loop [ stdout, stderr, exit_code.zero? ] end end
specs(context = nil)
click to toggle source
# File lib/deployer.rb, line 119 def specs(context = nil) spec_dir = self.spec_dir.presence || (context || default_context).to_s @specs[spec_dir] ||= begin spec_dir = "platform/#{spec_dir}/" paths = git.ls_tree(commit, spec_dir)['blob'].keys raise "No specs found in #{spec_dir}" unless paths.present? paths.map { |path| YAML.load_stream( git.show(commit, path) ) }.flatten.compact end end
specs_running(context = nil)
click to toggle source
# File lib/deployer.rb, line 53 def specs_running(context = nil) context = (context || default_context).to_s specs = deploy_specs(context) cmd = String.new "--output=json" if (namespace = specs.first.dig('metadata', 'namespace')) cmd += " --namespace #{namespace}" end cmd += " get" specs.each do |spec| cmd += " #{spec['kind'].downcase}/#{spec.dig('metadata', 'name')}" end statuses, stderr, success = kubectl(context, cmd) unless (success || stderr.match(/not found/)) && statuses.present? puts stderr if stderr.present? return nil end spec_status = specs.map { |spec| [ spec, nil ] }.to_h statuses = JSON.parse(statuses) statuses = statuses['items'] if statuses.key?('items') Array.wrap(statuses).each do |item| containers = Array(item.dig('spec', 'template', 'spec', 'containers')) + Array(item.dig('spec', 'jobTemplate', 'spec', 'template', 'spec', 'containers')) version = containers.first['image'].split(':').last # FIXME: support multiple containers status = item.delete('status').with_indifferent_access spec = specs.detect do |s| (item['kind'] == s['kind']) && (item.dig('metadata', 'name') == s.dig('metadata', 'name')) && (item.dig('metadata', 'namespace') == (s.dig('metadata', 'namespace') || 'default')) end spec_status[spec] = { spec: item, version: version, status: status } end spec_status end
Private Instance Methods
find_config(dir_or_path)
click to toggle source
# File lib/deployer.rb, line 180 def find_config(dir_or_path) return dir_or_path unless File.directory?(dir_or_path) CONFIG_LOCATIONS.map { |location| File.join(dir_or_path, location) }.detect { |path| File.exist?(path) } end
git()
click to toggle source
# File lib/deployer.rb, line 159 def git @git ||= Git.open(repository, log: nil) end
load_config()
click to toggle source
# File lib/deployer.rb, line 163 def load_config conf_path = find_config(root) conf = conf_path ? YAML.load_file(conf_path) : {} conf = conf.reverse_merge(CONFIG_DEFAULTS) @repository, @registry, @default_branch, @default_context, @production_context, @ssh_host, images = conf.values_at('repository', 'registry', 'default_branch', 'default_context', 'production_context', 'ssh', 'images').map(&:presence) @repository ||= root images ||= [{ 'name' => File.basename(File.absolute_path(repository)) }] @images = images.map do |image| name = image['name'] dockerfile = image['dockerfile'].presence || 'Dockerfile' Image.new(name: name, repository: repository, dockerfile: dockerfile, registry: registry, commit: nil, tag: nil) end end