shipitron 1.1.0 → 1.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.dockerignore +1 -0
- data/.gitattributes +1 -0
- data/.gitignore +1 -0
- data/Dockerfile +28 -5
- data/Dockerfile.release +28 -5
- data/Gemfile +1 -0
- data/README.md +48 -9
- data/build_dev.sh +1 -1
- data/docker-compose.yml +24 -0
- data/lib/shipitron/cli.rb +10 -4
- data/lib/shipitron/client.rb +9 -0
- data/lib/shipitron/client/bootstrap_application.rb +1 -0
- data/lib/shipitron/client/create_ecs_services.rb +7 -7
- data/lib/shipitron/client/deploy_application.rb +1 -0
- data/lib/shipitron/client/ensure_deploy_not_running.rb +2 -1
- data/lib/shipitron/client/fetch_clusters.rb +1 -0
- data/lib/shipitron/client/force_deploy.rb +1 -0
- data/lib/shipitron/client/load_application_config.rb +4 -0
- data/lib/shipitron/client/load_templates.rb +1 -0
- data/lib/shipitron/client/register_ecs_task_definitions.rb +5 -5
- data/lib/shipitron/client/run_ecs_tasks.rb +14 -1
- data/lib/shipitron/docker_image.rb +4 -1
- data/lib/shipitron/find_docker_volume_name.rb +68 -0
- data/lib/shipitron/git_info.rb +57 -0
- data/lib/shipitron/mustache_yaml_parser.rb +12 -8
- data/lib/shipitron/s3_copy.rb +46 -0
- data/lib/shipitron/server/deploy_application.rb +3 -0
- data/lib/shipitron/server/docker/build_image.rb +4 -1
- data/lib/shipitron/server/docker/configure.rb +46 -10
- data/lib/shipitron/server/docker/push_image.rb +3 -0
- data/lib/shipitron/server/docker/run_build_script.rb +17 -10
- data/lib/shipitron/server/download_build_cache.rb +17 -4
- data/lib/shipitron/server/git/clone_local_copy.rb +3 -4
- data/lib/shipitron/server/git/update_cache.rb +1 -0
- data/lib/shipitron/server/run_post_build.rb +40 -1
- data/lib/shipitron/server/transform_cli_args.rb +6 -0
- data/lib/shipitron/server/update_ecs_task_definitions.rb +2 -1
- data/lib/shipitron/server/upload_build_cache.rb +14 -8
- data/lib/shipitron/version.rb +1 -1
- data/scripts/docker-entrypoint.sh +9 -0
- data/shipitron.gemspec +8 -6
- metadata +53 -20
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'shipitron'
|
2
|
+
require 'shipitron/client'
|
2
3
|
require 'shipitron/ecs_client'
|
3
4
|
require 'shellwords'
|
4
5
|
require 'base64'
|
@@ -16,6 +17,7 @@ module Shipitron
|
|
16
17
|
required :shipitron_task
|
17
18
|
required :repository_url
|
18
19
|
required :s3_cache_bucket
|
20
|
+
required :build_cache_location
|
19
21
|
required :image_name
|
20
22
|
required :named_tag
|
21
23
|
required :ecs_task_defs
|
@@ -23,9 +25,11 @@ module Shipitron
|
|
23
25
|
optional :ecs_services
|
24
26
|
optional :ecs_service_templates
|
25
27
|
optional :build_script
|
28
|
+
optional :skip_push
|
26
29
|
optional :post_builds
|
27
30
|
optional :simulate
|
28
31
|
optional :repository_branch
|
32
|
+
optional :registry
|
29
33
|
|
30
34
|
before do
|
31
35
|
context.post_builds ||= []
|
@@ -72,7 +76,7 @@ module Shipitron
|
|
72
76
|
]
|
73
77
|
},
|
74
78
|
count: 1,
|
75
|
-
started_by:
|
79
|
+
started_by: Shipitron::Client::STARTED_BY
|
76
80
|
)
|
77
81
|
|
78
82
|
if !response.failures.empty?
|
@@ -108,6 +112,7 @@ module Shipitron
|
|
108
112
|
'--name', context.application,
|
109
113
|
'--repository', context.repository_url,
|
110
114
|
'--bucket', context.s3_cache_bucket,
|
115
|
+
'--build-cache-location', context.build_cache_location,
|
111
116
|
'--image-name', context.image_name,
|
112
117
|
'--named-tag', context.named_tag,
|
113
118
|
'--region', cluster.region,
|
@@ -123,10 +128,18 @@ module Shipitron
|
|
123
128
|
ary.concat(context.ecs_services)
|
124
129
|
end
|
125
130
|
|
131
|
+
if context.registry != nil
|
132
|
+
ary.concat ['--registry', context.registry]
|
133
|
+
end
|
134
|
+
|
126
135
|
if context.build_script != nil
|
127
136
|
ary.concat ['--build-script', context.build_script]
|
128
137
|
end
|
129
138
|
|
139
|
+
if context.skip_push != nil
|
140
|
+
ary.concat ['--skip-push', context.skip_push.to_s]
|
141
|
+
end
|
142
|
+
|
130
143
|
if !context.post_builds.empty?
|
131
144
|
ary << '--post-builds'
|
132
145
|
ary.concat(context.post_builds.map(&:to_s))
|
@@ -2,6 +2,7 @@ require 'shipitron'
|
|
2
2
|
|
3
3
|
module Shipitron
|
4
4
|
class DockerImage < Hashie::Dash
|
5
|
+
property :registry
|
5
6
|
property :name
|
6
7
|
property :tag
|
7
8
|
|
@@ -13,7 +14,9 @@ module Shipitron
|
|
13
14
|
tag_str = tag_str.dup.prepend(':')
|
14
15
|
end
|
15
16
|
|
16
|
-
|
17
|
+
name_with_registry = [registry, name].compact.join('/')
|
18
|
+
|
19
|
+
"#{name_with_registry}#{tag_str}"
|
17
20
|
end
|
18
21
|
|
19
22
|
def to_s
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'excon'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Shipitron
|
5
|
+
class FindDockerVolumeName
|
6
|
+
include Metaractor
|
7
|
+
|
8
|
+
required :container_name
|
9
|
+
required :volume_search
|
10
|
+
|
11
|
+
def call
|
12
|
+
volumes = container_volumes(container_name: container_name)
|
13
|
+
|
14
|
+
volume_metadata = volumes.find do |volume|
|
15
|
+
volume['DockerName'] =~ volume_search
|
16
|
+
end
|
17
|
+
|
18
|
+
if volume_metadata.nil?
|
19
|
+
raise 'Unable to find shipitron-home volume!'
|
20
|
+
end
|
21
|
+
|
22
|
+
context.volume_name = volume_metadata['DockerName']
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def container_name
|
27
|
+
context.container_name
|
28
|
+
end
|
29
|
+
|
30
|
+
def volume_search
|
31
|
+
context.volume_search
|
32
|
+
end
|
33
|
+
|
34
|
+
def container_volumes(container_name:)
|
35
|
+
container_metadata = self.task_metadata['Containers'].find do |container|
|
36
|
+
container['Name'] == container_name
|
37
|
+
end
|
38
|
+
|
39
|
+
return {} if container_metadata.nil?
|
40
|
+
|
41
|
+
container_metadata['Volumes']
|
42
|
+
end
|
43
|
+
|
44
|
+
def task_metadata
|
45
|
+
return @task_metadata if defined?(@task_metadata)
|
46
|
+
|
47
|
+
begin
|
48
|
+
response = Excon.get(
|
49
|
+
"#{ENV['ECS_CONTAINER_METADATA_URI_V4']}/task",
|
50
|
+
expects: [200],
|
51
|
+
connect_timeout: 5,
|
52
|
+
read_timeout: 5,
|
53
|
+
write_timeout: 5,
|
54
|
+
tcp_nodelay: true
|
55
|
+
)
|
56
|
+
|
57
|
+
Logger.debug "Metadata result:"
|
58
|
+
Logger.debug(response.body)
|
59
|
+
Logger.debug "\n"
|
60
|
+
|
61
|
+
@task_metadata = JSON.parse(response.body)
|
62
|
+
rescue
|
63
|
+
Logger.info "Metadata uri failed"
|
64
|
+
{}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'shipitron'
|
2
|
+
require 'rugged'
|
3
|
+
|
4
|
+
module Shipitron
|
5
|
+
class GitInfo < Hashie::Dash
|
6
|
+
property :sha
|
7
|
+
property :short_sha
|
8
|
+
property :email
|
9
|
+
property :name
|
10
|
+
property :summary
|
11
|
+
property :timestamp
|
12
|
+
property :branch
|
13
|
+
property :tag
|
14
|
+
|
15
|
+
def one_liner
|
16
|
+
"#{name} (#{short_sha}): #{summary}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.from_path(path:)
|
20
|
+
repo = Rugged::Repository.new(path)
|
21
|
+
commit = repo.last_commit
|
22
|
+
self.new(
|
23
|
+
sha: commit.oid,
|
24
|
+
short_sha: commit.oid[0, 12],
|
25
|
+
email: commit.author.dig(:email),
|
26
|
+
name: commit.author.dig(:name),
|
27
|
+
summary: commit.summary,
|
28
|
+
timestamp: commit.epoch_time.to_s,
|
29
|
+
branch: branch_name(repo: repo),
|
30
|
+
tag: tag_name(repo: repo)
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.branch_name(repo:)
|
35
|
+
ref = repo.head
|
36
|
+
ref.branch? ? ref.name.sub('refs/heads/', '') : nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.tag_name(repo:)
|
40
|
+
ref = repo.head
|
41
|
+
tags = repo.tags
|
42
|
+
|
43
|
+
tags.each do |tag|
|
44
|
+
target_id =
|
45
|
+
if tag.annotated?
|
46
|
+
tag.annotation.target_id
|
47
|
+
else
|
48
|
+
tag.target.oid
|
49
|
+
end
|
50
|
+
|
51
|
+
return tag.name if ref.target_id == target_id
|
52
|
+
end
|
53
|
+
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -3,20 +3,24 @@ require 'yaml'
|
|
3
3
|
|
4
4
|
module Shipitron
|
5
5
|
class MustacheYamlParser
|
6
|
-
def initialize(
|
7
|
-
|
6
|
+
def initialize(file_path, options = {})
|
7
|
+
@context = options[:context]
|
8
|
+
@view = options[:view]
|
9
|
+
|
10
|
+
if (@context.nil? && @view.nil?) || (!@context.nil? && !@view.nil?)
|
8
11
|
raise ArgumentError, 'Either context or view required'
|
9
12
|
end
|
10
13
|
|
11
|
-
@
|
12
|
-
@view = view
|
13
|
-
|
14
|
+
@file_path = file_path.is_a?(Pathname) ? file_path.to_s : file_path
|
14
15
|
@view ||= Mustache
|
15
16
|
end
|
16
17
|
|
17
|
-
def perform
|
18
|
-
|
19
|
-
|
18
|
+
def perform
|
19
|
+
YAML.load(@view.render(File.read(@file_path), @context))
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.perform(file_path, options = {})
|
23
|
+
new(file_path, options).perform
|
20
24
|
end
|
21
25
|
end
|
22
26
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'shipitron'
|
2
|
+
require 'shipitron/find_docker_volume_name'
|
3
|
+
|
4
|
+
module Shipitron
|
5
|
+
class S3Copy
|
6
|
+
include Metaractor
|
7
|
+
|
8
|
+
required :source
|
9
|
+
required :destination
|
10
|
+
required :region
|
11
|
+
|
12
|
+
def call
|
13
|
+
if ENV['FOG_LOCAL']
|
14
|
+
Logger.info `cp #{source.gsub('s3://', '/fog/')} #{destination.gsub('s3://', '/fog/')}`
|
15
|
+
if $? != 0
|
16
|
+
fail_with_error!(message: 'Failed to transfer to/from s3 (mocked).')
|
17
|
+
end
|
18
|
+
else
|
19
|
+
Logger.info "S3 Copy from #{source} to #{destination}"
|
20
|
+
|
21
|
+
shipitron_home_volume = FindDockerVolumeName.call!(
|
22
|
+
container_name: 'shipitron',
|
23
|
+
volume_search: /shipitron-home/
|
24
|
+
).volume_name
|
25
|
+
|
26
|
+
Logger.info `docker run --rm -t -v #{shipitron_home_volume}:/home/shipitron -e AWS_CONTAINER_CREDENTIALS_RELATIVE_URI amazon/aws-cli:latest --region #{region} s3 cp #{source} #{destination} --quiet --only-show-errors`
|
27
|
+
if $? != 0
|
28
|
+
fail_with_error!(message: 'Failed to transfer to/from s3.')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def source
|
35
|
+
context.source
|
36
|
+
end
|
37
|
+
|
38
|
+
def destination
|
39
|
+
context.destination
|
40
|
+
end
|
41
|
+
|
42
|
+
def region
|
43
|
+
context.region
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -19,6 +19,7 @@ module Shipitron
|
|
19
19
|
required :application
|
20
20
|
required :repository_url
|
21
21
|
required :s3_cache_bucket
|
22
|
+
required :build_cache_location
|
22
23
|
required :docker_image
|
23
24
|
required :named_tag
|
24
25
|
required :region
|
@@ -30,6 +31,8 @@ module Shipitron
|
|
30
31
|
optional :build_script
|
31
32
|
optional :post_builds
|
32
33
|
optional :repository_branch
|
34
|
+
optional :skip_push, default: false
|
35
|
+
optional :registry
|
33
36
|
|
34
37
|
around do |interactor|
|
35
38
|
if ENV['CONSUL_HOST'].nil?
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'shipitron'
|
2
2
|
require 'shipitron/consul_keys'
|
3
|
+
require 'json'
|
3
4
|
|
4
5
|
module Shipitron
|
5
6
|
module Server
|
@@ -9,22 +10,48 @@ module Shipitron
|
|
9
10
|
include ConsulKeys
|
10
11
|
|
11
12
|
required :application
|
13
|
+
optional :registry
|
12
14
|
|
13
15
|
before do
|
14
16
|
configure_consul_client!
|
15
17
|
end
|
16
18
|
|
17
19
|
def call
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
20
|
+
username = fetch_scoped_key('docker_user')
|
21
|
+
password = fetch_scoped_key('docker_password')
|
22
|
+
|
23
|
+
if username && password
|
24
|
+
Logger.info `docker login --username #{username} --password #{password}`
|
25
|
+
if $? != 0
|
26
|
+
fail_with_error!(message: 'Docker login failed.')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
if registry
|
31
|
+
case registry
|
32
|
+
when /docker\.io/
|
33
|
+
# do nothing
|
34
|
+
when /\d+\.dkr\.ecr\.us-east-1\.amazonaws\.com/
|
35
|
+
# ECR
|
36
|
+
config_file = Pathname.new('/home/shipitron/.docker/config.json')
|
37
|
+
config_file.parent.mkpath
|
38
|
+
|
39
|
+
config_hash = {}
|
40
|
+
if config_file.file?
|
41
|
+
config_file.open('rb') do |file|
|
42
|
+
json = file.read
|
43
|
+
config_hash = JSON.parse(json) rescue {}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
config_hash['credHelpers'] ||= {}
|
48
|
+
config_hash['credHelpers'][registry] = 'ecr-login'
|
49
|
+
|
50
|
+
config_file.open('wb') do |file|
|
51
|
+
file.puts(JSON.generate(config_hash))
|
52
|
+
file.chmod(0600)
|
53
|
+
end
|
54
|
+
end
|
28
55
|
end
|
29
56
|
end
|
30
57
|
|
@@ -33,6 +60,15 @@ module Shipitron
|
|
33
60
|
context.application
|
34
61
|
end
|
35
62
|
|
63
|
+
def registry
|
64
|
+
context.registry
|
65
|
+
end
|
66
|
+
|
67
|
+
def fetch_scoped_key(key)
|
68
|
+
value = fetch_key(key: "shipitron/#{application}/#{key}")
|
69
|
+
value = fetch_key(key: "shipitron/#{key}") if value.nil?
|
70
|
+
value
|
71
|
+
end
|
36
72
|
end
|
37
73
|
end
|
38
74
|
end
|
@@ -8,8 +8,11 @@ module Shipitron
|
|
8
8
|
|
9
9
|
required :docker_image
|
10
10
|
required :named_tag
|
11
|
+
optional :skip_push, default: false
|
11
12
|
|
12
13
|
def call
|
14
|
+
return if context.skip_push
|
15
|
+
|
13
16
|
Logger.info "Pushing docker image #{docker_image} and #{docker_image.name_with_tag(named_tag)}"
|
14
17
|
|
15
18
|
Logger.info `docker tag #{docker_image} #{docker_image.name_with_tag(named_tag)}`
|
@@ -9,17 +9,16 @@ module Shipitron
|
|
9
9
|
|
10
10
|
required :application
|
11
11
|
required :docker_image
|
12
|
-
required :
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
context.build_script ||= 'shipitron/build.sh'
|
17
|
-
end
|
12
|
+
required :git_info
|
13
|
+
required :named_tag
|
14
|
+
optional :build_script, default: 'shipitron/build.sh'
|
15
|
+
optional :registry
|
18
16
|
|
19
17
|
def call
|
20
18
|
Logger.info 'Building docker image'
|
21
19
|
|
22
|
-
docker_image.
|
20
|
+
docker_image.registry = registry if registry != nil
|
21
|
+
docker_image.tag = git_info.short_sha
|
23
22
|
|
24
23
|
FileUtils.cd("/home/shipitron/#{application}") do
|
25
24
|
unless Pathname.new(build_script).exist?
|
@@ -27,7 +26,7 @@ module Shipitron
|
|
27
26
|
end
|
28
27
|
|
29
28
|
cmd = TTY::Command.new
|
30
|
-
result = cmd.run!("#{build_script} #{docker_image}")
|
29
|
+
result = cmd.run!("#{build_script} #{docker_image} #{named_tag}")
|
31
30
|
|
32
31
|
if result.failure?
|
33
32
|
fail_with_error!(message: "build script exited with non-zero code: #{result.exit_status}")
|
@@ -44,13 +43,21 @@ module Shipitron
|
|
44
43
|
context.docker_image
|
45
44
|
end
|
46
45
|
|
47
|
-
def
|
48
|
-
context.
|
46
|
+
def git_info
|
47
|
+
context.git_info
|
48
|
+
end
|
49
|
+
|
50
|
+
def named_tag
|
51
|
+
context.named_tag
|
49
52
|
end
|
50
53
|
|
51
54
|
def build_script
|
52
55
|
context.build_script
|
53
56
|
end
|
57
|
+
|
58
|
+
def registry
|
59
|
+
context.registry
|
60
|
+
end
|
54
61
|
end
|
55
62
|
end
|
56
63
|
end
|