Add support for multiple deployment types
This commit is contained in:
parent
c4b08ff530
commit
481404b0b9
8 changed files with 270 additions and 136 deletions
15
src/config.cr
Normal file
15
src/config.cr
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
|
||||||
|
require "yaml"
|
||||||
|
require "./config/local"
|
||||||
|
require "./config/remote"
|
||||||
|
require "./config/deployment"
|
||||||
|
|
||||||
|
class Config
|
||||||
|
YAML.mapping(
|
||||||
|
locals: Array(LocalConfig),
|
||||||
|
remotes: Array(RemoteConfig),
|
||||||
|
deployments: Array(DeploymentConfig)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
10
src/config/deployment.cr
Normal file
10
src/config/deployment.cr
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
require "yaml"
|
||||||
|
|
||||||
|
class DeploymentConfig
|
||||||
|
YAML.mapping(
|
||||||
|
local: String,
|
||||||
|
remote: String,
|
||||||
|
type: String,
|
||||||
|
)
|
||||||
|
end
|
21
src/config/local.cr
Normal file
21
src/config/local.cr
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
|
||||||
|
require "yaml"
|
||||||
|
|
||||||
|
enum LocalType
|
||||||
|
DOCKER_IMAGE = 1
|
||||||
|
MYSQL_DUMP = 2
|
||||||
|
|
||||||
|
def to_yaml(io)
|
||||||
|
to_s(io)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class LocalConfig
|
||||||
|
YAML.mapping(
|
||||||
|
name: String,
|
||||||
|
type: LocalType, # enum ?
|
||||||
|
docker_image: String | Nil,
|
||||||
|
path: String | Nil
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
11
src/config/remote.cr
Normal file
11
src/config/remote.cr
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
|
||||||
|
require "yaml"
|
||||||
|
|
||||||
|
class RemoteConfig
|
||||||
|
YAML.mapping(
|
||||||
|
name: String,
|
||||||
|
user: String,
|
||||||
|
host: String
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
4
src/deployment.cr
Normal file
4
src/deployment.cr
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
require "./deployment/docker_image_to_dokku_app"
|
||||||
|
require "./deployment/mysql_dump_to_dokku_mariadb"
|
||||||
|
|
109
src/deployment/docker_image_to_dokku_app.cr
Normal file
109
src/deployment/docker_image_to_dokku_app.cr
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
|
||||||
|
require "colorize"
|
||||||
|
|
||||||
|
class DockerImageToDokkuApp
|
||||||
|
def self.handler
|
||||||
|
"docker_image_to_dokku_app"
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(@local : LocalConfig, @remote : RemoteConfig, @deployment : DeploymentConfig)
|
||||||
|
end
|
||||||
|
|
||||||
|
def run
|
||||||
|
image_meta = image_tag(@local.docker_image, config["app"])
|
||||||
|
image_push(@remote.host, image_meta["tag_name_version"])
|
||||||
|
image_deploy(@remote.host, image_meta["app"], image_meta["version"])
|
||||||
|
end
|
||||||
|
|
||||||
|
# private def image_tag(docker_compose_yml : String, service : String, app : String)
|
||||||
|
# version = `date +"v%Y%m%d_%H%M"`.strip
|
||||||
|
# tag_name = "dokku/#{app}"
|
||||||
|
# tag_name_version = "#{tag_name}:#{version}"
|
||||||
|
# image = `docker-compose -f #{docker_compose_yml} images -q #{service} `.strip
|
||||||
|
# Process.run "docker", ["tag", image, tag_name_version]
|
||||||
|
|
||||||
|
# res = {
|
||||||
|
# app: app,
|
||||||
|
# version: version,
|
||||||
|
# tag_name_version: tag_name_version
|
||||||
|
# }
|
||||||
|
# puts YAML.dump({ image_tag: res })
|
||||||
|
# puts "---"
|
||||||
|
# return res
|
||||||
|
# end
|
||||||
|
|
||||||
|
private def image_tag(docker_image : String, app : String)
|
||||||
|
version = `date +"v%Y%m%d_%H%M"`.strip
|
||||||
|
tag_name = "dokku/#{app}"
|
||||||
|
tag_name_version = "#{tag_name}:#{version}"
|
||||||
|
Process.run "docker", ["tag", docker_image, tag_name_version]
|
||||||
|
|
||||||
|
res = {
|
||||||
|
app: app,
|
||||||
|
version: version,
|
||||||
|
tag_name_version: tag_name_version
|
||||||
|
}
|
||||||
|
puts YAML.dump({ image_tag: res })
|
||||||
|
puts "---"
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
|
||||||
|
private def image_push(host, tag_name_version)
|
||||||
|
# docker save "$TAG_NAME_VERSION" \
|
||||||
|
# | gzip \
|
||||||
|
# | ssh "$HOST_REMOTE" "gunzip | docker load"
|
||||||
|
|
||||||
|
pipe1_reader, pipe1_writer = IO.pipe(true)
|
||||||
|
pipe2_reader, pipe2_writer = IO.pipe(true)
|
||||||
|
|
||||||
|
p3_out = IO::Memory.new
|
||||||
|
puts "Pushing image...".colorize(:yellow)
|
||||||
|
p3 = Process.new "ssh", [host, "gunzip | docker load"],
|
||||||
|
input: pipe2_reader, output: p3_out, error: STDERR
|
||||||
|
|
||||||
|
p2 = Process.new "gzip",
|
||||||
|
input: pipe1_reader,
|
||||||
|
output: pipe2_writer,
|
||||||
|
error: STDERR
|
||||||
|
|
||||||
|
p1 = Process.new "docker", ["save", tag_name_version],
|
||||||
|
output: pipe1_writer,
|
||||||
|
error: STDERR
|
||||||
|
|
||||||
|
status = p1.wait
|
||||||
|
pipe1_writer.close
|
||||||
|
if status.success?
|
||||||
|
puts "-----> Docker image successfully exported"
|
||||||
|
else
|
||||||
|
STDERR.puts "Error (code #{status.exit_status}) when exporting docker image!"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
status = p2.wait
|
||||||
|
pipe1_reader.close
|
||||||
|
pipe2_writer.close
|
||||||
|
if ! status.success?
|
||||||
|
STDERR.puts "Error (code #{status.exit_status}) when gzipping image!"
|
||||||
|
end
|
||||||
|
|
||||||
|
status = p3.wait
|
||||||
|
pipe2_reader.close
|
||||||
|
if status.success?
|
||||||
|
puts "-----> Docker image successfully imported on #{host}"
|
||||||
|
else
|
||||||
|
STDERR.puts "Error (code #{status.exit_status}) when importing docker image!"
|
||||||
|
end
|
||||||
|
puts "Image pushed successfully!".colorize(:green)
|
||||||
|
end
|
||||||
|
|
||||||
|
private def image_deploy(host, app, version)
|
||||||
|
puts "Deploying image #{app}:#{version}...".colorize(:yellow)
|
||||||
|
status = Process.run "ssh", [host, "dokku tags:deploy #{app} #{version}"],
|
||||||
|
output: STDOUT, error: STDOUT
|
||||||
|
if status.success?
|
||||||
|
puts "Image deployed successfully!".colorize(:green)
|
||||||
|
else
|
||||||
|
STDERR.puts "Error (code #{status.exit_status}) when deploying image!"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
13
src/deployment/mysql_dump_to_dokku_mariadb.cr
Normal file
13
src/deployment/mysql_dump_to_dokku_mariadb.cr
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
|
||||||
|
class MysqlDumpToDokkuMariadb
|
||||||
|
def self.handler
|
||||||
|
"mysql_dump_to_dokku_mariadb"
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(@local : LocalConfig, @remote : RemoteConfig, @deployment : DeploymentConfig)
|
||||||
|
end
|
||||||
|
|
||||||
|
def run
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
223
src/pushokku.cr
223
src/pushokku.cr
|
@ -3,157 +3,108 @@ require "option_parser"
|
||||||
require "yaml"
|
require "yaml"
|
||||||
require "colorize"
|
require "colorize"
|
||||||
|
|
||||||
|
require "./config"
|
||||||
|
require "./deployment"
|
||||||
|
|
||||||
|
module Pushokku
|
||||||
class Pushokku
|
class Cli
|
||||||
alias Options = {
|
alias Options = {
|
||||||
config_file: String,
|
config_file: String,
|
||||||
docker_compose_yml: String,
|
docker_compose_yml: String,
|
||||||
environment: String
|
environment: String
|
||||||
}
|
|
||||||
|
|
||||||
alias Config = {
|
|
||||||
host: String,
|
|
||||||
service: String,
|
|
||||||
app: String
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse_options(args) : Options
|
|
||||||
config_file = ".pushokku.yml"
|
|
||||||
docker_compose_yml = "docker-compose.yml"
|
|
||||||
environment = "production"
|
|
||||||
|
|
||||||
OptionParser.parse(args) do |parser|
|
|
||||||
parser.banner = "Welcome to Pushokku!"
|
|
||||||
|
|
||||||
parser.on "-c CONFIG", "--config=CONFIG", "Use the following config file" do |file|
|
|
||||||
config_file = file
|
|
||||||
end
|
|
||||||
|
|
||||||
parser.on "-f DOCKER_COMPOSE_YML", "--config=DOCKER_COMPOSE_YML", "Use the following docker-compose file" do |file|
|
|
||||||
docker_compose_yml = file
|
|
||||||
end
|
|
||||||
|
|
||||||
parser.on "-v", "--version", "Show version" do
|
|
||||||
puts "version 1.0"
|
|
||||||
exit
|
|
||||||
end
|
|
||||||
parser.on "-h", "--help", "Show help" do
|
|
||||||
puts parser
|
|
||||||
exit
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return {
|
|
||||||
docker_compose_yml: docker_compose_yml,
|
|
||||||
config_file: config_file,
|
|
||||||
environment: environment
|
|
||||||
}
|
}
|
||||||
end
|
|
||||||
|
|
||||||
def load_config(config_file : String) : Config
|
def parse_options(args) : Options
|
||||||
puts "Loading configuration...".colorize(:yellow)
|
config_file = ".pushokku.yml"
|
||||||
if ! File.exists? config_file
|
docker_compose_yml = "docker-compose.yml"
|
||||||
STDERR.puts "ERROR: Unable to read configuration file '#{config_file}'"
|
environment = "production"
|
||||||
exit 1
|
|
||||||
|
OptionParser.parse(args) do |parser|
|
||||||
|
parser.banner = "Welcome to Pushokku!"
|
||||||
|
|
||||||
|
parser.on "-c CONFIG", "--config=CONFIG", "Use the following config file" do |file|
|
||||||
|
config_file = file
|
||||||
|
end
|
||||||
|
|
||||||
|
parser.on "-f DOCKER_COMPOSE_YML", "--config=DOCKER_COMPOSE_YML", "Use the following docker-compose file" do |file|
|
||||||
|
docker_compose_yml = file
|
||||||
|
end
|
||||||
|
|
||||||
|
parser.on "-v", "--version", "Show version" do
|
||||||
|
puts "version 1.0"
|
||||||
|
exit
|
||||||
|
end
|
||||||
|
parser.on "-h", "--help", "Show help" do
|
||||||
|
puts parser
|
||||||
|
exit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return {
|
||||||
|
docker_compose_yml: docker_compose_yml,
|
||||||
|
config_file: config_file,
|
||||||
|
environment: environment
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
yaml = File.open(config_file) do |file|
|
def load_config(config_file : String) : Config
|
||||||
YAML.parse(file)
|
puts "Loading configuration...".colorize(:yellow)
|
||||||
|
if ! File.exists? config_file
|
||||||
|
STDERR.puts "ERROR: Unable to read configuration file '#{config_file}'"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
yaml_str = File.read(config_file)
|
||||||
|
config = Config.from_yaml(yaml_str)
|
||||||
|
# yaml = YAML.parse(yaml_str)
|
||||||
|
|
||||||
|
if config.nil?
|
||||||
|
STDERR.puts "ERROR: Invalid YAML content in '#{config_file}'"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return config
|
||||||
end
|
end
|
||||||
|
|
||||||
{
|
|
||||||
host: yaml["host"].to_s,
|
|
||||||
service: yaml["service"].to_s,
|
|
||||||
app: yaml["app"].to_s
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def image_tag(docker_compose_yml : String, service : String, app : String)
|
def self.run(args)
|
||||||
version = `date +"v%Y%m%d_%H%M"`.strip
|
app = Cli.new
|
||||||
tag_name = "dokku/#{app}"
|
opts = app.parse_options(args)
|
||||||
tag_name_version = "#{tag_name}:#{version}"
|
config = app.load_config(opts["config_file"])
|
||||||
image = `docker-compose -f #{docker_compose_yml} images -q #{service} `.strip
|
# env_config = App.get_config(config, opts["environment"])
|
||||||
Process.run "docker", ["tag", image, tag_name_version]
|
|
||||||
|
|
||||||
res = {
|
deployment_classes = [
|
||||||
app: app,
|
DockerImageToDokkuApp,
|
||||||
version: version,
|
MysqlDumpToDokkuMariadb
|
||||||
tag_name_version: tag_name_version
|
]
|
||||||
}
|
|
||||||
puts YAML.dump({ image_tag: res })
|
|
||||||
puts "---"
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
def image_push(host, tag_name_version)
|
config.deployments.each do |deployment|
|
||||||
# docker save "$TAG_NAME_VERSION" \
|
local = config.locals.select { |l| l.name == deployment.local }.first
|
||||||
# | gzip \
|
remote = config.remotes.select { |r| r.name == deployment.remote }.first
|
||||||
# | ssh "$HOST_REMOTE" "gunzip | docker load"
|
if local.nil?
|
||||||
|
puts "Unknown local #{deployment.local}. Exiting."
|
||||||
|
exit 2
|
||||||
|
end
|
||||||
|
if remote.nil?
|
||||||
|
puts "Unknown remote #{deployment.remote}. Exiting."
|
||||||
|
exit 2
|
||||||
|
end
|
||||||
|
|
||||||
pipe1_reader, pipe1_writer = IO.pipe(true)
|
deployment_handler = "#{local.type}_to_#{deployment.type}"
|
||||||
pipe2_reader, pipe2_writer = IO.pipe(true)
|
deployment_class = deployment_classes.select {|c| c.handler == deployment_handler }.first
|
||||||
|
if deployment_class.nil?
|
||||||
|
puts "Unknown deloyment class for #{deployment_handler}. Exiting."
|
||||||
|
exit 2
|
||||||
|
end
|
||||||
|
|
||||||
p3_out = IO::Memory.new
|
deployment = deployment_class.new(local, remote, deployment)
|
||||||
puts "Pushing image...".colorize(:yellow)
|
deployment.run
|
||||||
p3 = Process.new "ssh", [host, "gunzip | docker load"],
|
# puts deployment.inspect
|
||||||
input: pipe2_reader, output: p3_out, error: STDERR
|
end
|
||||||
|
|
||||||
p2 = Process.new "gzip",
|
exit 2
|
||||||
input: pipe1_reader,
|
|
||||||
output: pipe2_writer,
|
|
||||||
error: STDERR
|
|
||||||
|
|
||||||
p1 = Process.new "docker", ["save", tag_name_version],
|
|
||||||
output: pipe1_writer,
|
|
||||||
error: STDERR
|
|
||||||
|
|
||||||
status = p1.wait
|
|
||||||
pipe1_writer.close
|
|
||||||
if status.success?
|
|
||||||
puts "-----> Docker image successfully exported"
|
|
||||||
else
|
|
||||||
STDERR.puts "Error (code #{status.exit_status}) when exporting docker image!"
|
|
||||||
exit 1
|
|
||||||
end
|
end
|
||||||
|
|
||||||
status = p2.wait
|
|
||||||
pipe1_reader.close
|
|
||||||
pipe2_writer.close
|
|
||||||
if ! status.success?
|
|
||||||
STDERR.puts "Error (code #{status.exit_status}) when gzipping image!"
|
|
||||||
end
|
|
||||||
|
|
||||||
status = p3.wait
|
|
||||||
pipe2_reader.close
|
|
||||||
if status.success?
|
|
||||||
puts "-----> Docker image successfully imported on #{host}"
|
|
||||||
else
|
|
||||||
STDERR.puts "Error (code #{status.exit_status}) when importing docker image!"
|
|
||||||
end
|
|
||||||
puts "Image pushed successfully!".colorize(:green)
|
|
||||||
end
|
|
||||||
|
|
||||||
def image_deploy(host, app, version)
|
|
||||||
puts "Deploying image #{app}:#{version}...".colorize(:yellow)
|
|
||||||
status = Process.run "ssh", [host, "dokku tags:deploy #{app} #{version}"],
|
|
||||||
output: STDOUT, error: STDOUT
|
|
||||||
if status.success?
|
|
||||||
puts "Image deployed successfully!".colorize(:green)
|
|
||||||
else
|
|
||||||
STDERR.puts "Error (code #{status.exit_status}) when deploying image!"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.run(args)
|
|
||||||
app = Pushokku.new
|
|
||||||
opts = app.parse_options(args)
|
|
||||||
config = app.load_config(opts["config_file"])
|
|
||||||
# env_config = App.get_config(config, opts["environment"])
|
|
||||||
image_meta = app.image_tag(opts["docker_compose_yml"], config["service"], config["app"])
|
|
||||||
app.image_push(config["host"], image_meta["tag_name_version"])
|
|
||||||
app.image_deploy(config["host"], image_meta["app"], image_meta["version"])
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Pushokku.run(ARGV)
|
Pushokku::Cli.run(ARGV)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue