Code ist jetzt objektorientiert; man kann mehrere Container auf einmal starten.
This commit is contained in:
parent
bf53c905e5
commit
2377131483
328
dup.rb
328
dup.rb
@ -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,8 +189,175 @@ def action_completion(base_dir, complete_str)
|
||||
puts containers.join(" ")
|
||||
end
|
||||
|
||||
def action_test(container)
|
||||
c_data = `docker inspect #{container}`
|
||||
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
|
||||
|
||||
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
|
||||
@ -307,7 +375,8 @@ def action_test(container)
|
||||
data[name] = result
|
||||
end
|
||||
|
||||
puts ({container=>data}.to_yaml.lines[1..-1].join())
|
||||
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=="")
|
||||
if needs_container
|
||||
if container.empty?
|
||||
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
|
||||
container.each(&:load)
|
||||
end
|
||||
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
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user