Code ist jetzt objektorientiert; man kann mehrere Container auf einmal starten.

This commit is contained in:
Fabian Schlenz 2019-04-17 09:19:05 +02:00
parent bf53c905e5
commit 2377131483
1 changed files with 213 additions and 149 deletions

362
dup.rb
View File

@ -114,10 +114,11 @@ def action_help
puts "-p, --pull (Try to) Pull the image(s) before starting the container."
puts "-l, --list Lists all available containers."
puts " --host Starts a container with net: host - which implies not using ports, networks and/or links."
puts " --regenerate Generates YAML content from a running container."
exit 1
end
def run(cmd, ignore_returnvalue=false, catch_interrupt=false)
def run_cmd(cmd, ignore_returnvalue=false, catch_interrupt=false)
puts "+ #{cmd}" if $dry_run
returnvalue=false
begin
@ -133,7 +134,8 @@ def esc(obj, escape=true)
return obj.to_s.shellescape
end
def action_create(container, file)
def action_create(container)
file = container.file
raise "File #{file} already exists" if File.exists?(file)
File.open(file, "w") {|f| f.write(get_sample(container))}
@ -142,110 +144,9 @@ def action_create(container, file)
exit 1 # will never be reached because exec replaces this process. But just in case... ;-)
end
def action_run(container, file)
def action_run(container)
# $net_host
raise "File #{file} not found." unless File.exists?(file)
data = File.open(file, "r") {|f| YAML.load(f.read)}
raise "Expected #{file} to define (at least) a container named #{container}." unless data.has_key?(container)
data.each do |key, data|
if data["build"]
(data["before_build"] || []).each{|cmd| run(cmd)}
cmd = ["docker", "build", "-t", data["image"].shellescape, data["build"].shellescape]
run(cmd.join(" "))
(data["after_build"] || []).each{|cmd| run(cmd)}
end
if !data["test"]
if data["labels"]==nil
data["labels"] = ["de.fabianonline.dup=true"]
elsif data["labels"].is_a? Array
data["labels"] << "de.fabianonline.dup=true"
elsif data["labels"].is_a? Hash
data["labels"]["de.fabianonline.dup"] = "true"
else
raise "data['labels'] is of an unexpected type: #{data["labels"].class}"
end
end
(data["before_run"] || []).each{|cmd| run(cmd)}
cmd = ["docker", "create"]
cmd << "--net" << data["networks"][0] if data["networks"] && !$net_host
cmd << "--net" << "host" if $net_host
MAPPINGS.each do |mapping|
yml_name, cmd_name, reverse_lambda, options = *mapping
next if $net_host && %w(links net ports).include?(yml_name)
next if options && options[:type]==:hidden
options ||= {}
if !data[yml_name]
if options[:type]==:name
cmd << cmd_name << esc(key)
end
next
end
if options[:allow_empty] && data[yml_name]==""
cmd << cmd_name << '""'
next
end
if options[:type]==:switch
cmd << cmd_name
next
end
if data[yml_name].is_a?(Array)
data[yml_name].each {|val| cmd << cmd_name << esc(val, options[:escape])}
elsif data[yml_name].is_a?(Hash)
data[yml_name].each {|key, val| cmd << cmd_name << "#{esc(key, options[:escape])}=\"#{esc(val, options[:escape])}\""}
else
cmd << cmd_name << esc(data[yml_name], options[:escape])
end
end
if data["pull"] || $pull
run("docker pull #{data["image"].shellescape}", true)
end
puts "Stopping and removing old container..."
run("docker rm -f #{(data["container_name"] || key).shellescape} >/dev/null 2>&1", true)
if !$net_host && (data["networks"]||=[]).count>0
networks = `docker network ls --format '{{.Name}}'`.split
data["networks"].each do |network|
if networks.include?(network)
puts "Network #{network} exists."
else
puts "Creating network #{network}..."
run("docker network create #{network.shellescape}", true)
end
end
end
puts "Creating container..."
run(cmd.compact.join(" ") + " >/dev/null")
if !$net_host && data["networks"].count>0
data["networks"].each do |network|
puts "Connecting container to network #{network}..."
run("docker network connect #{network.shellescape} #{(data["container_name"] || key).shellescape} >/dev/null", true)
end
end
puts "Starting container..."
run("docker start #{(data["container_name"] || key).shellescape} >/dev/null")
(data["after_run"] || []).each{|cmd| run(cmd)}
if ! data["detach"]
puts "Attaching container..."
run("docker attach #{(data["container_name"] || key).shellescape}")
else
puts "Opening log... (press Ctrl-C to exit)"
puts
run("docker logs -f #{(data["container_name"] || key).shellescape}", true, true)
end
end
container.each{|c| c.run(container.count>1)}
end
def list(base_dir)
@ -288,26 +189,194 @@ def action_completion(base_dir, complete_str)
puts containers.join(" ")
end
def action_test(container)
c_data = `docker inspect #{container}`
c = JSON.parse(c_data, object_class: OpenStruct).first
i_data = `docker inspect #{c.Image}`
i = JSON.parse(i_data, object_class: OpenStruct).first
data = {}
MAPPINGS.each do |m|
name, cmd, lmbd, opts = *m
next unless lmbd
result = lmbd.call(c, i)
next if opts && opts[:type]==:switch && result==false
next if result==nil || result==[]
data[name] = result
def action_regenerate(container)
container.each do |c|
data = c.regenerate
puts data.to_yaml
puts
end
end
class Container
def initialize(name)
@name = name
end
puts ({container=>data}.to_yaml.lines[1..-1].join())
def load
raise "File #{self.filename} not found." unless File.exists?(self.filename)
@data = File.open(self.filename, "r") {|f| YAML.load(f.read)}
if @data.has_key?(@name)
if @data.keys.count > 1
raise "Expected #{self.filename} to contain all data on top level or underneath a single root element named #{@name}."
end
@data = @data[@name]
end
end
def filename; "%s/%s.yml" % [$base_dir, @name]; end
def build_run_command
cmd = ["docker", "create"]
cmd << "--net" << @data["networks"][0] if @data["networks"] && !$net_host
cmd << "--net" << "host" if $net_host
if !@data["test"]
if @data["labels"]==nil
@data["labels"] = ["de.fabianonline.dup=true"]
elsif @data["labels"].is_a? Array
@data["labels"] << "de.fabianonline.dup=true"
elsif @data["labels"].is_a? Hash
@data["labels"]["de.fabianonline.dup"] = "true"
else
raise "data['labels'] is of an unexpected type: #{@data["labels"].class}"
end
end
MAPPINGS.each do |mapping|
yml_name, cmd_name, reverse_lambda, options = *mapping
next if $net_host && %w(links net ports).include?(yml_name)
next if options && options[:type]==:hidden
options ||= {}
if !@data[yml_name]
if options[:type]==:name
cmd << cmd_name << esc(@name)
end
next
end
if options[:allow_empty] && @data[yml_name]==""
cmd << cmd_name << '""'
next
end
if options[:type]==:switch
cmd << cmd_name
next
end
if @data[yml_name].is_a?(Array)
@data[yml_name].each {|val| cmd << cmd_name << esc(val, options[:escape])}
elsif @data[yml_name].is_a?(Hash)
@data[yml_name].each {|key, val| cmd << cmd_name << "#{esc(key, options[:escape])}=\"#{esc(val, options[:escape])}\""}
else
cmd << cmd_name << esc(@data[yml_name], options[:escape])
end
end
return cmd
end
def build
(@data["before_build"] || []).each{|cmd| run_cmd(cmd)}
cmd = ["docker", "build", "-t", @data["image"].shellescape, @data["build"].shellescape]
run_cmd(cmd.join(" "))
(@data["after_build"] || []).each{|cmd| run_cmd(cmd)}
end
def pull
run_cmd("docker pull #{@data["image"].shellescape}", true)
end
def stop_and_remove
puts "Stopping and removing old container..."
run_cmd("docker rm -f #{@name.shellescape} >/dev/null 2>&1", true)
end
def create_networks
if !$net_host && (@data["networks"]||=[]).count>0
networks = `docker network ls --format '{{.Name}}'`.split
@data["networks"].each do |network|
if networks.include?(network)
puts "Network #{network} exists."
else
puts "Creating network #{network}..."
run_cmd("docker network create #{network.shellescape}", true)
end
end
end
end
def connect_to_networks
if !$net_host && @data["networks"].count>0
@data["networks"].each do |network|
puts "Connecting container to network #{network}..."
run_cmd("docker network connect #{network.shellescape} #{@name.shellescape} >/dev/null", true)
end
end
end
def attach
puts "Attaching container..."
run_cmd("docker attach #{@name.shellescape}")
end
def log
puts "Opening log... (press Ctrl-C to exit)"
puts
run_cmd("docker logs -f #{@name.shellescape}", true, true)
end
def run(force_background = false)
raise "No data loaded. Maybe call .load first?" if @data.nil?
if @data["build"]
self.build
end
(@data["before_run"] || []).each{|cmd| run_cmd(cmd)}
if @data["pull"] || $pull
self.pull
end
self.stop_and_remove
self.create_networks
puts "Creating container..."
cmd = build_run_command
run_cmd(cmd.compact.join(" ") + " >/dev/null")
self.connect_to_networks
puts "Starting container..."
run_cmd("docker start #{@name.shellescape} >/dev/null")
(@data["after_run"] || []).each{|cmd| run_cmd(cmd)}
if !force_background
if ! @data["detach"]
self.attach
else
self.log
end
end
end
def regenerate
c_data = `docker inspect #{@name}`
c = JSON.parse(c_data, object_class: OpenStruct).first
i_data = `docker inspect #{c.Image}`
i = JSON.parse(i_data, object_class: OpenStruct).first
data = {}
MAPPINGS.each do |m|
name, cmd, lmbd, opts = *m
next unless lmbd
result = lmbd.call(c, i)
next if opts && opts[:type]==:switch && result==false
next if result==nil || result==[]
data[name] = result
end
return data
end
end
action = :run
@ -315,11 +384,17 @@ container = nil
$dry_run = false
$pull = false
needs_container = true
needs_basedir = true
completion_str = ""
silent_basedir = false
$net_host = false
if ENV['DUP_DIR']
$base_dir = ENV['DUP_DIR']
else
$base_dir = File.join(Dir.home, ".dup")
puts "Environment variable DUP_DIR is not set. Looking for .yml files in #{base_dir}" unless silent_basedir
end
opts = GetoptLong.new(
[ '--sample', '-s', GetoptLong::NO_ARGUMENT ],
[ '--create', '-c', GetoptLong::NO_ARGUMENT ],
@ -330,7 +405,7 @@ opts = GetoptLong.new(
[ '--update', '-u', GetoptLong::NO_ARGUMENT ],
[ '--_completion', GetoptLong::OPTIONAL_ARGUMENT ],
[ '--host', GetoptLong::NO_ARGUMENT ],
[ '--test', GetoptLong::NO_ARGUMENT ]
[ '--regenerate', GetoptLong::NO_ARGUMENT ]
)
opts.each do |opt, arg|
@ -339,7 +414,6 @@ opts.each do |opt, arg|
action_sample
when '--create'
action = :create
needs_basedir = true
when '--help'
action_help
when '--dry-run'
@ -353,50 +427,40 @@ opts.each do |opt, arg|
when '--update'
action = :update
needs_container = false
needs_basedir = true
when '--_completion'
action = :_completion
completion_str = arg
needs_container = false
needs_basedir = true
silent_basedir = true
when '--host'
$net_host = true
when '--test'
action = :test
needs_basedir = false
when '--regenerate'
action = :regenerate
end
end
container = ARGV.shift
container = ARGV.map{|c| Container.new(c)}
if needs_container && (container==nil || container=="")
raise "No container given."
end
if needs_basedir
if ENV['DUP_DIR']
base_dir = ENV['DUP_DIR']
else
base_dir = File.join(Dir.home, ".dup")
puts "Environment variable DUP_DIR is not set. Looking for .yml files in #{base_dir}" unless silent_basedir
if needs_container
if container.empty?
raise "No container given."
end
container.each(&:load)
end
file = "%s/%s.yml" % [ base_dir, container ]
if action == :create
action_create(container, file)
action_create(container)
elsif action == :run
action_run(container, file)
action_run(container)
elsif action == :list
action_list(base_dir)
elsif action == :update
action_update(base_dir)
elsif action == :test
action_test(container)
elsif action == :regenerate
action_regenerate(container)
elsif action == :_completion
action_completion(base_dir, completion_str)
action_completion($base_dir, completion_str)
end