Code ist jetzt objektorientiert; man kann mehrere Container auf einmal starten.
This commit is contained in:
parent
bf53c905e5
commit
2377131483
362
dup.rb
362
dup.rb
@ -114,10 +114,11 @@ def action_help
|
|||||||
puts "-p, --pull (Try to) Pull the image(s) before starting the container."
|
puts "-p, --pull (Try to) Pull the image(s) before starting the container."
|
||||||
puts "-l, --list Lists all available containers."
|
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 " --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
|
exit 1
|
||||||
end
|
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
|
puts "+ #{cmd}" if $dry_run
|
||||||
returnvalue=false
|
returnvalue=false
|
||||||
begin
|
begin
|
||||||
@ -133,7 +134,8 @@ def esc(obj, escape=true)
|
|||||||
return obj.to_s.shellescape
|
return obj.to_s.shellescape
|
||||||
end
|
end
|
||||||
|
|
||||||
def action_create(container, file)
|
def action_create(container)
|
||||||
|
file = container.file
|
||||||
raise "File #{file} already exists" if File.exists?(file)
|
raise "File #{file} already exists" if File.exists?(file)
|
||||||
|
|
||||||
File.open(file, "w") {|f| f.write(get_sample(container))}
|
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... ;-)
|
exit 1 # will never be reached because exec replaces this process. But just in case... ;-)
|
||||||
end
|
end
|
||||||
|
|
||||||
def action_run(container, file)
|
def action_run(container)
|
||||||
# $net_host
|
# $net_host
|
||||||
raise "File #{file} not found." unless File.exists?(file)
|
container.each{|c| c.run(container.count>1)}
|
||||||
|
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def list(base_dir)
|
def list(base_dir)
|
||||||
@ -288,26 +189,194 @@ def action_completion(base_dir, complete_str)
|
|||||||
puts containers.join(" ")
|
puts containers.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
def action_test(container)
|
def action_regenerate(container)
|
||||||
c_data = `docker inspect #{container}`
|
container.each do |c|
|
||||||
c = JSON.parse(c_data, object_class: OpenStruct).first
|
data = c.regenerate
|
||||||
i_data = `docker inspect #{c.Image}`
|
puts data.to_yaml
|
||||||
i = JSON.parse(i_data, object_class: OpenStruct).first
|
puts
|
||||||
|
end
|
||||||
data = {}
|
end
|
||||||
|
|
||||||
MAPPINGS.each do |m|
|
class Container
|
||||||
name, cmd, lmbd, opts = *m
|
def initialize(name)
|
||||||
|
@name = name
|
||||||
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
|
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
|
end
|
||||||
|
|
||||||
action = :run
|
action = :run
|
||||||
@ -315,11 +384,17 @@ container = nil
|
|||||||
$dry_run = false
|
$dry_run = false
|
||||||
$pull = false
|
$pull = false
|
||||||
needs_container = true
|
needs_container = true
|
||||||
needs_basedir = true
|
|
||||||
completion_str = ""
|
completion_str = ""
|
||||||
silent_basedir = false
|
silent_basedir = false
|
||||||
$net_host = 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(
|
opts = GetoptLong.new(
|
||||||
[ '--sample', '-s', GetoptLong::NO_ARGUMENT ],
|
[ '--sample', '-s', GetoptLong::NO_ARGUMENT ],
|
||||||
[ '--create', '-c', GetoptLong::NO_ARGUMENT ],
|
[ '--create', '-c', GetoptLong::NO_ARGUMENT ],
|
||||||
@ -330,7 +405,7 @@ opts = GetoptLong.new(
|
|||||||
[ '--update', '-u', GetoptLong::NO_ARGUMENT ],
|
[ '--update', '-u', GetoptLong::NO_ARGUMENT ],
|
||||||
[ '--_completion', GetoptLong::OPTIONAL_ARGUMENT ],
|
[ '--_completion', GetoptLong::OPTIONAL_ARGUMENT ],
|
||||||
[ '--host', GetoptLong::NO_ARGUMENT ],
|
[ '--host', GetoptLong::NO_ARGUMENT ],
|
||||||
[ '--test', GetoptLong::NO_ARGUMENT ]
|
[ '--regenerate', GetoptLong::NO_ARGUMENT ]
|
||||||
)
|
)
|
||||||
|
|
||||||
opts.each do |opt, arg|
|
opts.each do |opt, arg|
|
||||||
@ -339,7 +414,6 @@ opts.each do |opt, arg|
|
|||||||
action_sample
|
action_sample
|
||||||
when '--create'
|
when '--create'
|
||||||
action = :create
|
action = :create
|
||||||
needs_basedir = true
|
|
||||||
when '--help'
|
when '--help'
|
||||||
action_help
|
action_help
|
||||||
when '--dry-run'
|
when '--dry-run'
|
||||||
@ -353,50 +427,40 @@ opts.each do |opt, arg|
|
|||||||
when '--update'
|
when '--update'
|
||||||
action = :update
|
action = :update
|
||||||
needs_container = false
|
needs_container = false
|
||||||
needs_basedir = true
|
|
||||||
when '--_completion'
|
when '--_completion'
|
||||||
action = :_completion
|
action = :_completion
|
||||||
completion_str = arg
|
completion_str = arg
|
||||||
needs_container = false
|
needs_container = false
|
||||||
needs_basedir = true
|
|
||||||
silent_basedir = true
|
silent_basedir = true
|
||||||
when '--host'
|
when '--host'
|
||||||
$net_host = true
|
$net_host = true
|
||||||
when '--test'
|
when '--regenerate'
|
||||||
action = :test
|
action = :regenerate
|
||||||
needs_basedir = false
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
container = ARGV.shift
|
container = ARGV.map{|c| Container.new(c)}
|
||||||
|
|
||||||
if needs_container && (container==nil || container=="")
|
if needs_container
|
||||||
raise "No container given."
|
if container.empty?
|
||||||
end
|
raise "No container given."
|
||||||
|
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
container.each(&:load)
|
||||||
end
|
end
|
||||||
|
|
||||||
file = "%s/%s.yml" % [ base_dir, container ]
|
|
||||||
|
|
||||||
if action == :create
|
if action == :create
|
||||||
action_create(container, file)
|
action_create(container)
|
||||||
elsif action == :run
|
elsif action == :run
|
||||||
action_run(container, file)
|
action_run(container)
|
||||||
elsif action == :list
|
elsif action == :list
|
||||||
action_list(base_dir)
|
action_list(base_dir)
|
||||||
elsif action == :update
|
elsif action == :update
|
||||||
action_update(base_dir)
|
action_update(base_dir)
|
||||||
elsif action == :test
|
elsif action == :regenerate
|
||||||
action_test(container)
|
action_regenerate(container)
|
||||||
elsif action == :_completion
|
elsif action == :_completion
|
||||||
action_completion(base_dir, completion_str)
|
action_completion($base_dir, completion_str)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user