Compare commits
7 Commits
2377131483
...
eee7b009a4
Author | SHA1 | Date | |
---|---|---|---|
eee7b009a4 | |||
18eb80ebf6 | |||
1eb3a8ded7 | |||
73c3f5aacb | |||
1ee6de03c1 | |||
33a8df5ac1 | |||
61b3f2b98a |
106
dup.rb
106
dup.rb
@ -7,6 +7,16 @@ require 'ostruct'
|
|||||||
require 'json'
|
require 'json'
|
||||||
|
|
||||||
MAPPINGS = [
|
MAPPINGS = [
|
||||||
|
# Syntax:
|
||||||
|
# [name_in_yml, docker_option_name, lambda_for_creating_yml_for_container=nil, options={}]
|
||||||
|
# Options are:
|
||||||
|
# type: Sets type of this option
|
||||||
|
# switch: Only adds the docker_option_name to the command, ignoring the value.
|
||||||
|
# name: Value will be set to the name of the container (from yml filename or root element)
|
||||||
|
# hidden: Will not be added to the run command
|
||||||
|
# allow_empty: May be empty.
|
||||||
|
# escape: If set to false, will not be shellescaped
|
||||||
|
|
||||||
['stdin_open', '--interactive', lambda{|c,i| c.Config.OpenStdin}, {:type=>:switch}],
|
['stdin_open', '--interactive', lambda{|c,i| c.Config.OpenStdin}, {:type=>:switch}],
|
||||||
['tty', '--tty', lambda{|c,i| c.Config.Tty}, {:type=>:switch}],
|
['tty', '--tty', lambda{|c,i| c.Config.Tty}, {:type=>:switch}],
|
||||||
#['detach', '--detach', {:type=>:switch}],
|
#['detach', '--detach', {:type=>:switch}],
|
||||||
@ -23,6 +33,7 @@ MAPPINGS = [
|
|||||||
['devices', '--device'],
|
['devices', '--device'],
|
||||||
['net', '--net'],
|
['net', '--net'],
|
||||||
['ipv6', '--ipv6'],
|
['ipv6', '--ipv6'],
|
||||||
|
['user', '--user'],
|
||||||
['networks', '--network', lambda{|c,i| c.NetworkSettings.Networks.to_h.keys.map(&:to_s)}, {:type=>:hidden}],
|
['networks', '--network', lambda{|c,i| c.NetworkSettings.Networks.to_h.keys.map(&:to_s)}, {:type=>:hidden}],
|
||||||
['entrypoint', '--entrypoint', lambda{|c,i| (ep=c.Config.Entrypoint) == i.Config.Entrypoint ? nil : ep}, {:allow_empty=>true}],
|
['entrypoint', '--entrypoint', lambda{|c,i| (ep=c.Config.Entrypoint) == i.Config.Entrypoint ? nil : ep}, {:allow_empty=>true}],
|
||||||
['labels', '--label', lambda{|c,i| l=Hash[c.Config.Labels.to_h.delete_if{|k,v| i.Config.Labels[k]==v rescue false}.map{|k,v| [k.to_s, v]}] ; l}],
|
['labels', '--label', lambda{|c,i| l=Hash[c.Config.Labels.to_h.delete_if{|k,v| i.Config.Labels[k]==v rescue false}.map{|k,v| [k.to_s, v]}] ; l}],
|
||||||
@ -30,17 +41,19 @@ MAPPINGS = [
|
|||||||
['working_dir', '--workdir', lambda{|c,i| (wd=c.Config.WorkingDir) == i.Config.WorkingDir ? nil : wd}],
|
['working_dir', '--workdir', lambda{|c,i| (wd=c.Config.WorkingDir) == i.Config.WorkingDir ? nil : wd}],
|
||||||
['init', '--init', lambda{|c,i| c.Config.Init}, {:type=>:switch}],
|
['init', '--init', lambda{|c,i| c.Config.Init}, {:type=>:switch}],
|
||||||
['image', nil, lambda{|c,i| c.Config.Image}],
|
['image', nil, lambda{|c,i| c.Config.Image}],
|
||||||
['command', nil, lambda{|c,i| (cmd=c.Config.Cmd.join(" ") rescue nil) == (i.Config.Cmd.join(" ") rescue nil)? nil : cmd}, {:escape=>false}]
|
['command', nil, lambda{|c,i| (cmd=c.Config.Cmd.join(" ") rescue nil) == (i.Config.Cmd.join(" ") rescue nil)? nil : cmd}, {:escape=>false}],
|
||||||
|
['test', nil, nil, {type: :hidden}],
|
||||||
|
['auto_update', nil, nil, {type: :hidden}]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_sample(name="container")
|
def get_sample(name="container")
|
||||||
return <<~HEREDOC
|
return <<~HEREDOC
|
||||||
#{name}:
|
|
||||||
image: "my/image:1.2.3"
|
image: "my/image:1.2.3"
|
||||||
restart: always
|
restart: always
|
||||||
detach: true
|
detach: true
|
||||||
test: true # Having this set to true will prevent dup from creating a label de.fabianonline.dup
|
test: true # Having this set to true will prevent dup from creating a label de.fabianonline.dup
|
||||||
|
auto_update: true # Enabling this adds a label to allow watchtower to automatically update this container
|
||||||
|
|
||||||
ports:
|
ports:
|
||||||
- "1234:1234"
|
- "1234:1234"
|
||||||
@ -54,24 +67,22 @@ def get_sample(name="container")
|
|||||||
volumes:
|
volumes:
|
||||||
- "/etc/localtime:/etc/localtime:ro"
|
- "/etc/localtime:/etc/localtime:ro"
|
||||||
|
|
||||||
links:
|
|
||||||
- "another_container"
|
|
||||||
|
|
||||||
labels:
|
labels:
|
||||||
com.centurylinklabs.watchtower.enable: true
|
nginx_virtual_host: container.home.schle.nz
|
||||||
nginx_virtual_host: container.home.fabianonline.de
|
|
||||||
nginx_port: 80
|
nginx_port: 80
|
||||||
|
nginx_additional_ports: "443, 5050"
|
||||||
nginx_allow: fabian # "user" or "@group" or "user, user, @group" or "all"
|
nginx_allow: fabian # "user" or "@group" or "user, user, @group" or "all"
|
||||||
nginx_no_robots: true
|
nginx_no_robots: true
|
||||||
nginx_public_paths: "/public, /api"
|
nginx_public_paths: "/public, /api"
|
||||||
nginx_type: # "http" (default), "fastcgi", "skip" (doesn't create any entries)
|
nginx_type: # "http" (default), "https", "skip" (doesn't create any entries)
|
||||||
|
nginx_client_max_body_size: 25M
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
- "nginx"
|
- "nginx"
|
||||||
- "mosquitto"
|
- "mosquitto"
|
||||||
- "bridge"
|
- "bridge"
|
||||||
|
|
||||||
test: true
|
user: "1000:1000"
|
||||||
command: "/bin/bash"
|
command: "/bin/bash"
|
||||||
entrypoint: "/script.sh"
|
entrypoint: "/script.sh"
|
||||||
build: "/data/dir"
|
build: "/data/dir"
|
||||||
@ -115,18 +126,21 @@ def action_help
|
|||||||
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."
|
puts " --regenerate Generates YAML content from a running container."
|
||||||
|
puts " --all Use all containers defined in yml files."
|
||||||
|
puts "-v, --verbose More verbose output."
|
||||||
exit 1
|
exit 1
|
||||||
end
|
end
|
||||||
|
|
||||||
def run_cmd(cmd, ignore_returnvalue=false, catch_interrupt=false)
|
def run_cmd(cmd, ignore_returnvalue=false, catch_interrupt=false, ignore_dry_run: false)
|
||||||
puts "+ #{cmd}" if $dry_run
|
verbose "+ #{cmd}"
|
||||||
returnvalue=false
|
returnvalue=false
|
||||||
begin
|
begin
|
||||||
returnvalue = $dry_run ? true : system("bash -c #{cmd.shellescape}")
|
returnvalue = ($dry_run && !ignore_dry_run) ? true : system("bash -c #{cmd.shellescape}")
|
||||||
rescue Interrupt
|
rescue Interrupt
|
||||||
raise if not catch_interrupt
|
raise if not catch_interrupt
|
||||||
end
|
end
|
||||||
raise "Command returned a non-zero exit value." if returnvalue!=true && !ignore_returnvalue
|
raise "Command returned a non-zero exit value." if returnvalue!=true && !ignore_returnvalue
|
||||||
|
return returnvalue
|
||||||
end
|
end
|
||||||
|
|
||||||
def esc(obj, escape=true)
|
def esc(obj, escape=true)
|
||||||
@ -202,6 +216,10 @@ class Container
|
|||||||
@name = name
|
@name = name
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def exists?
|
||||||
|
run_cmd("docker inspect --format='1' #{@name.shellescape} >/dev/null 2>&1", true, ignore_dry_run: true)
|
||||||
|
end
|
||||||
|
|
||||||
def load
|
def load
|
||||||
raise "File #{self.filename} not found." unless File.exists?(self.filename)
|
raise "File #{self.filename} not found." unless File.exists?(self.filename)
|
||||||
|
|
||||||
@ -217,23 +235,32 @@ class Container
|
|||||||
|
|
||||||
def filename; "%s/%s.yml" % [$base_dir, @name]; end
|
def filename; "%s/%s.yml" % [$base_dir, @name]; end
|
||||||
|
|
||||||
def build_run_command
|
def add_label(key, value)
|
||||||
cmd = ["docker", "create"]
|
if @data["labels"].is_a? Array
|
||||||
cmd << "--net" << @data["networks"][0] if @data["networks"] && !$net_host
|
@data["labels"] << "#{key}=#{value}"
|
||||||
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
|
elsif @data["labels"].is_a? Hash
|
||||||
@data["labels"]["de.fabianonline.dup"] = "true"
|
@data["labels"][key] = value
|
||||||
else
|
else
|
||||||
raise "data['labels'] is of an unexpected type: #{@data["labels"].class}"
|
raise "data['labels'] is of an unexpected type: #{@data["labels"].class}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def build_run_command
|
||||||
|
$net_host = true if @data["net"]=="host"
|
||||||
|
cmd = ["docker", "create"]
|
||||||
|
cmd << "--net" << @data["networks"][0] if @data["networks"] && @data["networks"].count>0 && !$net_host
|
||||||
|
cmd << "--net" << "host" if $net_host
|
||||||
|
|
||||||
|
@data["labels"] ||= []
|
||||||
|
|
||||||
|
if !@data["test"]
|
||||||
|
add_label("de.fabianonline.dup", "true")
|
||||||
|
end
|
||||||
|
|
||||||
|
if @data["auto_update"]
|
||||||
|
add_label("com.centurylinklabs.watchtower.enable", "true")
|
||||||
|
end
|
||||||
|
|
||||||
MAPPINGS.each do |mapping|
|
MAPPINGS.each do |mapping|
|
||||||
yml_name, cmd_name, reverse_lambda, options = *mapping
|
yml_name, cmd_name, reverse_lambda, options = *mapping
|
||||||
next if $net_host && %w(links net ports).include?(yml_name)
|
next if $net_host && %w(links net ports).include?(yml_name)
|
||||||
@ -281,7 +308,7 @@ class Container
|
|||||||
end
|
end
|
||||||
|
|
||||||
def stop_and_remove
|
def stop_and_remove
|
||||||
puts "Stopping and removing old container..."
|
verbose "Stopping and removing old container..."
|
||||||
run_cmd("docker rm -f #{@name.shellescape} >/dev/null 2>&1", true)
|
run_cmd("docker rm -f #{@name.shellescape} >/dev/null 2>&1", true)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -290,9 +317,9 @@ class Container
|
|||||||
networks = `docker network ls --format '{{.Name}}'`.split
|
networks = `docker network ls --format '{{.Name}}'`.split
|
||||||
@data["networks"].each do |network|
|
@data["networks"].each do |network|
|
||||||
if networks.include?(network)
|
if networks.include?(network)
|
||||||
puts "Network #{network} exists."
|
verbose "Network #{network} exists."
|
||||||
else
|
else
|
||||||
puts "Creating network #{network}..."
|
verbose "Creating network #{network}..."
|
||||||
run_cmd("docker network create #{network.shellescape}", true)
|
run_cmd("docker network create #{network.shellescape}", true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -302,14 +329,14 @@ class Container
|
|||||||
def connect_to_networks
|
def connect_to_networks
|
||||||
if !$net_host && @data["networks"].count>0
|
if !$net_host && @data["networks"].count>0
|
||||||
@data["networks"].each do |network|
|
@data["networks"].each do |network|
|
||||||
puts "Connecting container to network #{network}..."
|
verbose "Connecting container to network #{network}..."
|
||||||
run_cmd("docker network connect #{network.shellescape} #{@name.shellescape} >/dev/null", true)
|
run_cmd("docker network connect #{network.shellescape} #{@name.shellescape} >/dev/null", true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def attach
|
def attach
|
||||||
puts "Attaching container..."
|
verbose "Attaching container..."
|
||||||
run_cmd("docker attach #{@name.shellescape}")
|
run_cmd("docker attach #{@name.shellescape}")
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -332,17 +359,23 @@ class Container
|
|||||||
self.pull
|
self.pull
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if self.exists?
|
||||||
|
puts "Recreating #{@name}..."
|
||||||
|
else
|
||||||
|
puts "Creating #{@name}..."
|
||||||
|
end
|
||||||
|
|
||||||
self.stop_and_remove
|
self.stop_and_remove
|
||||||
|
|
||||||
self.create_networks
|
self.create_networks
|
||||||
|
|
||||||
puts "Creating container..."
|
verbose "Creating container..."
|
||||||
cmd = build_run_command
|
cmd = build_run_command
|
||||||
run_cmd(cmd.compact.join(" ") + " >/dev/null")
|
run_cmd(cmd.compact.join(" ") + " >/dev/null")
|
||||||
|
|
||||||
self.connect_to_networks
|
self.connect_to_networks
|
||||||
|
|
||||||
puts "Starting container..."
|
verbose "Starting container..."
|
||||||
run_cmd("docker start #{@name.shellescape} >/dev/null")
|
run_cmd("docker start #{@name.shellescape} >/dev/null")
|
||||||
|
|
||||||
(@data["after_run"] || []).each{|cmd| run_cmd(cmd)}
|
(@data["after_run"] || []).each{|cmd| run_cmd(cmd)}
|
||||||
@ -379,9 +412,12 @@ class Container
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def verbose(str); puts(str) if $verbose; end
|
||||||
|
|
||||||
action = :run
|
action = :run
|
||||||
container = nil
|
container = nil
|
||||||
$dry_run = false
|
$dry_run = false
|
||||||
|
$verbose = false
|
||||||
$pull = false
|
$pull = false
|
||||||
needs_container = true
|
needs_container = true
|
||||||
completion_str = ""
|
completion_str = ""
|
||||||
@ -405,7 +441,9 @@ 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 ],
|
||||||
[ '--regenerate', GetoptLong::NO_ARGUMENT ]
|
[ '--regenerate', GetoptLong::NO_ARGUMENT ],
|
||||||
|
[ '--verbose', '-v', GetoptLong::NO_ARGUMENT ],
|
||||||
|
[ '--all', GetoptLong::NO_ARGUMENT ],
|
||||||
)
|
)
|
||||||
|
|
||||||
opts.each do |opt, arg|
|
opts.each do |opt, arg|
|
||||||
@ -436,10 +474,14 @@ opts.each do |opt, arg|
|
|||||||
$net_host = true
|
$net_host = true
|
||||||
when '--regenerate'
|
when '--regenerate'
|
||||||
action = :regenerate
|
action = :regenerate
|
||||||
|
when '--verbose'
|
||||||
|
$verbose = true
|
||||||
|
when '--all'
|
||||||
|
container = Dir[$base_dir + "/*.yml"].sort.map{|f| Container.new(File.basename(f, ".yml"))}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
container = ARGV.map{|c| Container.new(c)}
|
container ||= ARGV.map{|c| Container.new(c)}
|
||||||
|
|
||||||
if needs_container
|
if needs_container
|
||||||
if container.empty?
|
if container.empty?
|
||||||
|
Reference in New Issue
Block a user