Code ist jetzt objektorientiert; man kann mehrere Container auf einmal starten.
This commit is contained in:
		
							
								
								
									
										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 | ||||
|  | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user