shipitron 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.dockerignore +4 -0
- data/.gitignore +4 -0
- data/.rspec +3 -0
- data/Dockerfile +60 -0
- data/Dockerfile.release +49 -0
- data/README.md +44 -10
- data/build_dev.sh +39 -0
- data/exe/shipitron +5 -0
- data/lib/shipitron/cli.rb +132 -0
- data/lib/shipitron/client/bootstrap_application.rb +35 -0
- data/lib/shipitron/client/create_ecs_services.rb +81 -0
- data/lib/shipitron/client/deploy_application.rb +34 -0
- data/lib/shipitron/client/ensure_deploy_not_running.rb +54 -0
- data/lib/shipitron/client/load_application_config.rb +41 -0
- data/lib/shipitron/client/load_templates.rb +45 -0
- data/lib/shipitron/client/register_ecs_task_definitions.rb +75 -0
- data/lib/shipitron/client/run_ecs_tasks.rb +141 -0
- data/lib/shipitron/consul_keys.rb +34 -0
- data/lib/shipitron/consul_lock.rb +28 -0
- data/lib/shipitron/docker_image.rb +23 -0
- data/lib/shipitron/ecs_client.rb +22 -0
- data/lib/shipitron/ecs_task_def.rb +17 -0
- data/lib/shipitron/fetch_bucket.rb +26 -0
- data/lib/shipitron/logger.rb +33 -0
- data/lib/shipitron/mustache_yaml_parser.rb +22 -0
- data/lib/shipitron/parse_templates.rb +32 -0
- data/lib/shipitron/post_build.rb +32 -0
- data/lib/shipitron/server/deploy_application.rb +75 -0
- data/lib/shipitron/server/docker/build_image.rb +25 -0
- data/lib/shipitron/server/docker/configure.rb +35 -0
- data/lib/shipitron/server/docker/push_image.rb +37 -0
- data/lib/shipitron/server/docker/run_build_script.rb +53 -0
- data/lib/shipitron/server/download_build_cache.rb +44 -0
- data/lib/shipitron/server/ecs_task_defs/map_parsed_templates.rb +31 -0
- data/lib/shipitron/server/ecs_task_defs/update_from_params.rb +42 -0
- data/lib/shipitron/server/ecs_task_defs/update_in_place.rb +68 -0
- data/lib/shipitron/server/git/clone_local_copy.rb +46 -0
- data/lib/shipitron/server/git/configure.rb +40 -0
- data/lib/shipitron/server/git/download_cache.rb +56 -0
- data/lib/shipitron/server/git/pull_repo.rb +30 -0
- data/lib/shipitron/server/git/update_cache.rb +52 -0
- data/lib/shipitron/server/git/upload_cache.rb +61 -0
- data/lib/shipitron/server/run_post_build.rb +79 -0
- data/lib/shipitron/server/transform_cli_args.rb +85 -0
- data/lib/shipitron/server/update_ecs_services.rb +105 -0
- data/lib/shipitron/server/update_ecs_task_definitions.rb +47 -0
- data/lib/shipitron/server/upload_build_cache.rb +43 -0
- data/lib/shipitron/smash.rb +10 -0
- data/lib/shipitron/version.rb +1 -1
- data/lib/shipitron.rb +38 -0
- data/scripts/docker-entrypoint.sh +18 -0
- data/scripts/fetch-bundler-data.sh +16 -0
- data/shipitron.gemspec +14 -0
- metadata +236 -4
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
require 'shipitron'
|
|
2
|
+
require 'shipitron/ecs_client'
|
|
3
|
+
require 'shipitron/mustache_yaml_parser'
|
|
4
|
+
|
|
5
|
+
module Shipitron
|
|
6
|
+
module Client
|
|
7
|
+
class RegisterEcsTaskDefinitions
|
|
8
|
+
include Metaractor
|
|
9
|
+
include EcsClient
|
|
10
|
+
|
|
11
|
+
required :region
|
|
12
|
+
required :task_def_directory
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
Logger.info 'Creating ECS task definitions'
|
|
16
|
+
|
|
17
|
+
task_defs = Pathname.new(task_def_directory)
|
|
18
|
+
unless task_defs.directory?
|
|
19
|
+
fail_with_error!(
|
|
20
|
+
message: "task definition directory '#{task_def_directory}' does not exist"
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
task_defs.find do |path|
|
|
25
|
+
next if path.directory?
|
|
26
|
+
|
|
27
|
+
task_def = Smash.load(
|
|
28
|
+
path.to_s,
|
|
29
|
+
parser: MustacheYamlParser.new(
|
|
30
|
+
context: {
|
|
31
|
+
tag: 'latest'
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
begin
|
|
37
|
+
ecs_client(region: region).describe_task_definition(
|
|
38
|
+
task_definition: task_def.family
|
|
39
|
+
)
|
|
40
|
+
rescue Aws::ECS::Errors::ClientException => e
|
|
41
|
+
raise if e.message != 'Unable to describe task definition.'
|
|
42
|
+
|
|
43
|
+
Logger.info "Creating task definition '#{task_def.family}'"
|
|
44
|
+
Logger.debug "Task definition: #{task_def.to_h}"
|
|
45
|
+
ecs_client(region: region).register_task_definition(
|
|
46
|
+
task_def.to_h
|
|
47
|
+
)
|
|
48
|
+
else
|
|
49
|
+
Logger.info "Task definition '#{task_def.family}' already exists."
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
Logger.info 'Done'
|
|
54
|
+
rescue Aws::ECS::Errors::ServiceError => e
|
|
55
|
+
fail_with_errors!(messages: [
|
|
56
|
+
"Error: #{e.message}",
|
|
57
|
+
e.backtrace.join("\n")
|
|
58
|
+
])
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
def region
|
|
63
|
+
context.region
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def cluster_name
|
|
67
|
+
context.cluster_name
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def task_def_directory
|
|
71
|
+
context.task_def_directory
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
require 'shipitron'
|
|
2
|
+
require 'shipitron/ecs_client'
|
|
3
|
+
require 'shellwords'
|
|
4
|
+
require 'base64'
|
|
5
|
+
|
|
6
|
+
module Shipitron
|
|
7
|
+
module Client
|
|
8
|
+
class RunEcsTasks
|
|
9
|
+
include Metaractor
|
|
10
|
+
include EcsClient
|
|
11
|
+
|
|
12
|
+
required :application
|
|
13
|
+
required :clusters
|
|
14
|
+
required :shipitron_task
|
|
15
|
+
required :repository_url
|
|
16
|
+
required :s3_cache_bucket
|
|
17
|
+
required :image_name
|
|
18
|
+
required :ecs_task_defs
|
|
19
|
+
optional :ecs_task_def_templates
|
|
20
|
+
required :ecs_services
|
|
21
|
+
optional :ecs_service_templates
|
|
22
|
+
optional :build_script
|
|
23
|
+
optional :post_builds
|
|
24
|
+
optional :simulate
|
|
25
|
+
|
|
26
|
+
before do
|
|
27
|
+
context.post_builds ||= []
|
|
28
|
+
context.ecs_task_def_templates ||= []
|
|
29
|
+
context.ecs_service_templates ||= []
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def call
|
|
33
|
+
Logger.info "Skipping ECS run_task calls due to --simulate" if simulate?
|
|
34
|
+
|
|
35
|
+
clusters.each do |cluster|
|
|
36
|
+
begin
|
|
37
|
+
if simulate?
|
|
38
|
+
command_args(cluster)
|
|
39
|
+
next
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
response = ecs_client(region: cluster.region).run_task(
|
|
43
|
+
cluster: cluster.name,
|
|
44
|
+
task_definition: shipitron_task,
|
|
45
|
+
overrides: {
|
|
46
|
+
container_overrides: [
|
|
47
|
+
{
|
|
48
|
+
name: 'shipitron',
|
|
49
|
+
command: command_args(cluster)
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
count: 1,
|
|
54
|
+
started_by: 'shipitron'
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
if !response.failures.empty?
|
|
58
|
+
response.failures.each do |failure|
|
|
59
|
+
fail_with_error! message: "ECS run_task failure: #{failure.arn}: #{failure.reason}"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
rescue Aws::ECS::Errors::ServiceError => e
|
|
64
|
+
fail_with_errors!(messages: [
|
|
65
|
+
"Error: #{e.message}",
|
|
66
|
+
e.backtrace.join("\n")
|
|
67
|
+
])
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
def application
|
|
74
|
+
context.application
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def clusters
|
|
78
|
+
context.clusters
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def shipitron_task
|
|
82
|
+
context.shipitron_task
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def escape(str)
|
|
86
|
+
Shellwords.escape(str)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def escaped(sym)
|
|
90
|
+
escape(context[sym])
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def command_args(cluster)
|
|
94
|
+
[
|
|
95
|
+
'server_deploy',
|
|
96
|
+
'--name', escaped(:application),
|
|
97
|
+
'--repository', escaped(:repository_url),
|
|
98
|
+
'--bucket', escaped(:s3_cache_bucket),
|
|
99
|
+
'--image-name', escaped(:image_name),
|
|
100
|
+
'--region', escape(cluster.region),
|
|
101
|
+
'--cluster-name', escape(cluster.name),
|
|
102
|
+
].tap do |ary|
|
|
103
|
+
ary << '--ecs-task-defs'
|
|
104
|
+
ary.concat(context.ecs_task_defs.each {|s| escape(s)})
|
|
105
|
+
|
|
106
|
+
ary << '--ecs-services'
|
|
107
|
+
ary.concat(context.ecs_services.each {|s| escape(s)})
|
|
108
|
+
|
|
109
|
+
if context.build_script != nil
|
|
110
|
+
ary.concat ['--build-script', escaped(:build_script)]
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
if !context.post_builds.empty?
|
|
114
|
+
ary << '--post-builds'
|
|
115
|
+
ary.concat(context.post_builds.map(&:to_s).each {|s| escape(s)})
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
if !context.ecs_task_def_templates.empty?
|
|
119
|
+
ary << '--ecs-task-def-templates'
|
|
120
|
+
ary.concat(context.ecs_task_def_templates.map {|t| Base64.urlsafe_encode64(t)})
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
if !context.ecs_service_templates.empty?
|
|
124
|
+
ary << '--ecs-service-templates'
|
|
125
|
+
ary.concat(context.ecs_service_templates.map {|t| Base64.urlsafe_encode64(t)})
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
if simulate?
|
|
129
|
+
Logger.info "server_deploy command: #{ary.join(' ')}"
|
|
130
|
+
else
|
|
131
|
+
Logger.debug "server_deploy command: #{ary.join(' ')}"
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def simulate?
|
|
137
|
+
context.simulate == true
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require 'shipitron'
|
|
2
|
+
require 'diplomat'
|
|
3
|
+
|
|
4
|
+
module Shipitron
|
|
5
|
+
module ConsulKeys
|
|
6
|
+
extend self
|
|
7
|
+
|
|
8
|
+
def configure_consul_client!
|
|
9
|
+
if ENV['CONSUL_HOST'].nil?
|
|
10
|
+
raise 'Environment variable CONSUL_HOST required'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
Diplomat.configure do |config|
|
|
14
|
+
config.url = "http://#{ENV['CONSUL_HOST']}:8500"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def fetch_key(key:)
|
|
19
|
+
Logger.debug "Fetching key #{key}"
|
|
20
|
+
value = Diplomat::Kv.get(key, {}, :return)
|
|
21
|
+
value = nil if value == ''
|
|
22
|
+
value
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def fetch_key!(key:)
|
|
26
|
+
fetch_key(key: key).tap do |value|
|
|
27
|
+
if value.nil?
|
|
28
|
+
raise "Key #{key} not found in consul!"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'diplomat'
|
|
2
|
+
|
|
3
|
+
module Shipitron
|
|
4
|
+
module ConsulLock
|
|
5
|
+
extend self
|
|
6
|
+
|
|
7
|
+
class UnableToLock < StandardError; end
|
|
8
|
+
|
|
9
|
+
def with_lock(key:)
|
|
10
|
+
sessionid = nil
|
|
11
|
+
locked = false
|
|
12
|
+
sessionid = Diplomat::Session.create(Name: "#{key}.lock")
|
|
13
|
+
locked = Diplomat::Lock.acquire(key, sessionid)
|
|
14
|
+
|
|
15
|
+
if locked
|
|
16
|
+
yield
|
|
17
|
+
else
|
|
18
|
+
raise UnableToLock
|
|
19
|
+
end
|
|
20
|
+
ensure
|
|
21
|
+
if sessionid != nil
|
|
22
|
+
Diplomat::Lock.release(key, sessionid) if locked
|
|
23
|
+
Diplomat::Session.destroy(sessionid)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'shipitron'
|
|
2
|
+
|
|
3
|
+
module Shipitron
|
|
4
|
+
class DockerImage < Hashie::Dash
|
|
5
|
+
property :name
|
|
6
|
+
property :tag
|
|
7
|
+
|
|
8
|
+
def name_with_tag(tag_override = nil)
|
|
9
|
+
tag_str = [tag_override, tag, ''].find {|str| !str.nil? }
|
|
10
|
+
tag_str = tag_str.to_s
|
|
11
|
+
|
|
12
|
+
if !tag_str.empty? && !tag_str.start_with?(':')
|
|
13
|
+
tag_str = tag_str.dup.prepend(':')
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
"#{name}#{tag_str}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def to_s
|
|
20
|
+
name_with_tag
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require 'aws-sdk'
|
|
2
|
+
|
|
3
|
+
module Shipitron
|
|
4
|
+
module EcsClient
|
|
5
|
+
def ecs_client(region:)
|
|
6
|
+
@ecs_clients ||= {}
|
|
7
|
+
@ecs_clients[region] ||= generate_ecs_client(region: region)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def generate_ecs_client(region:)
|
|
11
|
+
options = {region: region}
|
|
12
|
+
if Shipitron.config.aws_access_key_id? && Shipitron.config.aws_secret_access_key
|
|
13
|
+
options.merge!(
|
|
14
|
+
access_key_id: Shipitron.config.aws_access_key_id,
|
|
15
|
+
secret_access_key: Shipitron.config.aws_secret_access_key
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
Aws::ECS::Client.new(options)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'shipitron'
|
|
2
|
+
|
|
3
|
+
module Shipitron
|
|
4
|
+
class EcsTaskDef < Hashie::Dash
|
|
5
|
+
property :name
|
|
6
|
+
property :revision
|
|
7
|
+
property :params
|
|
8
|
+
|
|
9
|
+
def name_with_revision
|
|
10
|
+
"#{name}:#{revision}"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def to_s
|
|
14
|
+
name_with_revision
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require 'fog/aws'
|
|
2
|
+
require 'fog/local' if ENV['FOG_LOCAL']
|
|
3
|
+
|
|
4
|
+
module Shipitron
|
|
5
|
+
class FetchBucket
|
|
6
|
+
include Metaractor
|
|
7
|
+
|
|
8
|
+
required :name
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
if ENV['FOG_LOCAL']
|
|
12
|
+
Logger.debug 'Using fog local storage'
|
|
13
|
+
storage = Fog::Storage.new provider: 'Local', local_root: '/fog'
|
|
14
|
+
context.bucket = storage.directories.create(key: name)
|
|
15
|
+
else
|
|
16
|
+
storage = Fog::Storage.new provider: 'AWS', use_iam_profile: true
|
|
17
|
+
context.bucket = storage.directories.get(name)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
def name
|
|
23
|
+
context.name
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require 'logger'
|
|
2
|
+
|
|
3
|
+
module Shipitron
|
|
4
|
+
class Logger
|
|
5
|
+
class << self
|
|
6
|
+
%i[debug info warn error fatal].each do |sym|
|
|
7
|
+
define_method(sym) do |message|
|
|
8
|
+
logger.send(sym, "#{Thread.current[:logger_tag]}#{message}")
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.tagged(tag)
|
|
14
|
+
existing_tag = Thread.current[:logger_tag]
|
|
15
|
+
Thread.current[:logger_tag] = "[#{tag}] "
|
|
16
|
+
yield
|
|
17
|
+
ensure
|
|
18
|
+
Thread.current[:logger_tag] = existing_tag
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.logger
|
|
22
|
+
Thread.current[:logger] ||= ::Logger.new(STDOUT)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.level
|
|
26
|
+
logger.level
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.level=(new_level)
|
|
30
|
+
logger.level = new_level
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require 'mustache'
|
|
2
|
+
require 'yaml'
|
|
3
|
+
|
|
4
|
+
module Shipitron
|
|
5
|
+
class MustacheYamlParser
|
|
6
|
+
def initialize(context:nil, view:nil)
|
|
7
|
+
if (context.nil? && view.nil?) || (!context.nil? && !view.nil?)
|
|
8
|
+
raise ArgumentError, 'Either context or view required'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
@context = context
|
|
12
|
+
@view = view
|
|
13
|
+
|
|
14
|
+
@view ||= Mustache
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def perform(file_path)
|
|
18
|
+
file_path = file_path.is_a?(Pathname) ? file_path.to_s : file_path
|
|
19
|
+
YAML.load(@view.render(File.read(file_path), @context))
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require 'shipitron'
|
|
2
|
+
require 'yaml'
|
|
3
|
+
require 'mustache'
|
|
4
|
+
|
|
5
|
+
module Shipitron
|
|
6
|
+
module Server
|
|
7
|
+
class ParseTemplates
|
|
8
|
+
include Metaractor
|
|
9
|
+
|
|
10
|
+
required :templates
|
|
11
|
+
required :template_context
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
parsed = []
|
|
15
|
+
templates.each do |template|
|
|
16
|
+
parsed << Smash.new(YAML.load(Mustache.render(template, template_context)))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
context.parsed_templates = parsed
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
def templates
|
|
24
|
+
context.templates
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def template_context
|
|
28
|
+
context.template_context
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require 'shipitron'
|
|
2
|
+
|
|
3
|
+
module Shipitron
|
|
4
|
+
class PostBuild < Hashie::Dash
|
|
5
|
+
property :ecs_task
|
|
6
|
+
property :container_name
|
|
7
|
+
property :command
|
|
8
|
+
|
|
9
|
+
# String is of the format:
|
|
10
|
+
# 'ecs_task:task,container_name:name,command:command
|
|
11
|
+
def self.parse(str)
|
|
12
|
+
PostBuild.new.tap do |post_build|
|
|
13
|
+
str.split(',').each do |part|
|
|
14
|
+
part.match(/([^:]+):(.+)/) do |m|
|
|
15
|
+
prop = m[1].to_sym
|
|
16
|
+
if property?(prop)
|
|
17
|
+
post_build[prop] = m[2]
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
properties.each do |prop|
|
|
23
|
+
raise "post build argument missing '#{prop}'" if post_build[prop].nil?
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def to_s
|
|
29
|
+
"ecs_task:#{ecs_task},container_name:#{container_name},command:#{command}"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
require 'shipitron'
|
|
2
|
+
require 'shipitron/consul_lock'
|
|
3
|
+
require 'shipitron/server/git/pull_repo'
|
|
4
|
+
require 'shipitron/server/docker/configure'
|
|
5
|
+
require 'shipitron/server/docker/build_image'
|
|
6
|
+
require 'shipitron/server/docker/push_image'
|
|
7
|
+
require 'shipitron/server/update_ecs_task_definitions'
|
|
8
|
+
require 'shipitron/server/run_post_build'
|
|
9
|
+
require 'shipitron/server/update_ecs_services'
|
|
10
|
+
|
|
11
|
+
module Shipitron
|
|
12
|
+
module Server
|
|
13
|
+
class DeployApplication
|
|
14
|
+
include Metaractor
|
|
15
|
+
include Interactor::Organizer
|
|
16
|
+
include ConsulLock
|
|
17
|
+
|
|
18
|
+
required :application
|
|
19
|
+
required :repository_url
|
|
20
|
+
required :s3_cache_bucket
|
|
21
|
+
required :docker_image
|
|
22
|
+
required :region
|
|
23
|
+
required :cluster_name
|
|
24
|
+
required :ecs_task_defs
|
|
25
|
+
optional :ecs_task_def_templates
|
|
26
|
+
required :ecs_services
|
|
27
|
+
optional :ecs_service_templates
|
|
28
|
+
optional :build_script
|
|
29
|
+
optional :post_builds
|
|
30
|
+
optional :repository_branch
|
|
31
|
+
|
|
32
|
+
around do |interactor|
|
|
33
|
+
if ENV['CONSUL_HOST'].nil?
|
|
34
|
+
fail_with_error!(message: 'Environment variable CONSUL_HOST required')
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
Diplomat.configure do |config|
|
|
38
|
+
config.url = "http://#{ENV['CONSUL_HOST']}:8500"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
begin
|
|
42
|
+
with_lock(key: "shipitron/#{application}/deploy_lock") do
|
|
43
|
+
interactor.call
|
|
44
|
+
end
|
|
45
|
+
rescue UnableToLock
|
|
46
|
+
fail_with_errors!(messages: [
|
|
47
|
+
'Shipitron says: THERE CAN BE ONLY ONE',
|
|
48
|
+
'Unable to acquire deploy lock.'
|
|
49
|
+
])
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
organize [
|
|
54
|
+
Git::PullRepo,
|
|
55
|
+
Docker::Configure,
|
|
56
|
+
Docker::BuildImage,
|
|
57
|
+
Docker::PushImage,
|
|
58
|
+
UpdateEcsTaskDefinitions,
|
|
59
|
+
RunPostBuild,
|
|
60
|
+
UpdateEcsServices
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
def call
|
|
64
|
+
Logger.info "==> Deploying #{application} (server-side)"
|
|
65
|
+
super
|
|
66
|
+
Logger.info "==> Done"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
def application
|
|
71
|
+
context.application
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'shipitron'
|
|
2
|
+
require 'shipitron/server/download_build_cache'
|
|
3
|
+
require 'shipitron/server/docker/run_build_script'
|
|
4
|
+
require 'shipitron/server/upload_build_cache'
|
|
5
|
+
|
|
6
|
+
module Shipitron
|
|
7
|
+
module Server
|
|
8
|
+
module Docker
|
|
9
|
+
class BuildImage
|
|
10
|
+
include Metaractor
|
|
11
|
+
include Interactor::Organizer
|
|
12
|
+
|
|
13
|
+
required :application
|
|
14
|
+
required :docker_image
|
|
15
|
+
required :git_sha
|
|
16
|
+
|
|
17
|
+
organize [
|
|
18
|
+
DownloadBuildCache,
|
|
19
|
+
Docker::RunBuildScript,
|
|
20
|
+
UploadBuildCache
|
|
21
|
+
]
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require 'shipitron'
|
|
2
|
+
require 'shipitron/consul_keys'
|
|
3
|
+
|
|
4
|
+
module Shipitron
|
|
5
|
+
module Server
|
|
6
|
+
module Docker
|
|
7
|
+
class Configure
|
|
8
|
+
include Metaractor
|
|
9
|
+
include ConsulKeys
|
|
10
|
+
|
|
11
|
+
required :application
|
|
12
|
+
|
|
13
|
+
before do
|
|
14
|
+
configure_consul_client!
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def call
|
|
18
|
+
docker_auth = fetch_key!(key: "shipitron/#{application}/docker_auth")
|
|
19
|
+
auth_file = Pathname.new('/home/shipitron/.docker/config.json')
|
|
20
|
+
auth_file.parent.mkpath
|
|
21
|
+
auth_file.open('wb') do |file|
|
|
22
|
+
file.puts(docker_auth.to_s)
|
|
23
|
+
file.chmod(0600)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
def application
|
|
29
|
+
context.application
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require 'shipitron'
|
|
2
|
+
|
|
3
|
+
module Shipitron
|
|
4
|
+
module Server
|
|
5
|
+
module Docker
|
|
6
|
+
class PushImage
|
|
7
|
+
include Metaractor
|
|
8
|
+
|
|
9
|
+
required :docker_image
|
|
10
|
+
|
|
11
|
+
def call
|
|
12
|
+
Logger.info "Pushing docker image #{docker_image} and #{docker_image.name_with_tag(:latest)}"
|
|
13
|
+
|
|
14
|
+
Logger.info `docker tag #{docker_image} #{docker_image.name_with_tag(:latest)}`
|
|
15
|
+
if $? != 0
|
|
16
|
+
fail_with_error!(message: 'Docker tag failed.')
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
Logger.info `docker push #{docker_image}`
|
|
20
|
+
if $? != 0
|
|
21
|
+
fail_with_error!(message: 'Docker push failed.')
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
Logger.info `docker push #{docker_image.name_with_tag(:latest)}`
|
|
25
|
+
if $? != 0
|
|
26
|
+
fail_with_error!(message: 'Docker push (latest) failed.')
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
def docker_image
|
|
32
|
+
context.docker_image
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|