shipitron 1.1.0 → 1.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.dockerignore +1 -0
  3. data/.gitattributes +1 -0
  4. data/.gitignore +1 -0
  5. data/Dockerfile +28 -5
  6. data/Dockerfile.release +28 -5
  7. data/Gemfile +1 -0
  8. data/README.md +48 -9
  9. data/build_dev.sh +1 -1
  10. data/docker-compose.yml +24 -0
  11. data/lib/shipitron/cli.rb +10 -4
  12. data/lib/shipitron/client.rb +9 -0
  13. data/lib/shipitron/client/bootstrap_application.rb +1 -0
  14. data/lib/shipitron/client/create_ecs_services.rb +7 -7
  15. data/lib/shipitron/client/deploy_application.rb +1 -0
  16. data/lib/shipitron/client/ensure_deploy_not_running.rb +2 -1
  17. data/lib/shipitron/client/fetch_clusters.rb +1 -0
  18. data/lib/shipitron/client/force_deploy.rb +1 -0
  19. data/lib/shipitron/client/load_application_config.rb +4 -0
  20. data/lib/shipitron/client/load_templates.rb +1 -0
  21. data/lib/shipitron/client/register_ecs_task_definitions.rb +5 -5
  22. data/lib/shipitron/client/run_ecs_tasks.rb +14 -1
  23. data/lib/shipitron/docker_image.rb +4 -1
  24. data/lib/shipitron/find_docker_volume_name.rb +68 -0
  25. data/lib/shipitron/git_info.rb +57 -0
  26. data/lib/shipitron/mustache_yaml_parser.rb +12 -8
  27. data/lib/shipitron/s3_copy.rb +46 -0
  28. data/lib/shipitron/server/deploy_application.rb +3 -0
  29. data/lib/shipitron/server/docker/build_image.rb +4 -1
  30. data/lib/shipitron/server/docker/configure.rb +46 -10
  31. data/lib/shipitron/server/docker/push_image.rb +3 -0
  32. data/lib/shipitron/server/docker/run_build_script.rb +17 -10
  33. data/lib/shipitron/server/download_build_cache.rb +17 -4
  34. data/lib/shipitron/server/git/clone_local_copy.rb +3 -4
  35. data/lib/shipitron/server/git/update_cache.rb +1 -0
  36. data/lib/shipitron/server/run_post_build.rb +40 -1
  37. data/lib/shipitron/server/transform_cli_args.rb +6 -0
  38. data/lib/shipitron/server/update_ecs_task_definitions.rb +2 -1
  39. data/lib/shipitron/server/upload_build_cache.rb +14 -8
  40. data/lib/shipitron/version.rb +1 -1
  41. data/scripts/docker-entrypoint.sh +9 -0
  42. data/shipitron.gemspec +8 -6
  43. 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: 'shipitron'
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
- "#{name}#{tag_str}"
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(context:nil, view:nil)
7
- if (context.nil? && view.nil?) || (!context.nil? && !view.nil?)
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
- @context = context
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(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))
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?
@@ -12,7 +12,10 @@ module Shipitron
12
12
 
13
13
  required :application
14
14
  required :docker_image
15
- required :git_sha
15
+ required :git_info
16
+ required :named_tag
17
+ required :region
18
+ optional :registry
16
19
 
17
20
  organize [
18
21
  DownloadBuildCache,
@@ -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
- docker_auth = begin
19
- key = fetch_key(key: "shipitron/#{application}/docker_auth")
20
- key = fetch_key!(key: 'shipitron/docker_auth') if key.nil?
21
- key
22
- end
23
- auth_file = Pathname.new('/home/shipitron/.docker/config.json')
24
- auth_file.parent.mkpath
25
- auth_file.open('wb') do |file|
26
- file.puts(docker_auth.to_s)
27
- file.chmod(0600)
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 :git_sha
13
- optional :build_script
14
-
15
- before do
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.tag = git_sha
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 git_sha
48
- context.git_sha
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