shiplane 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f140db779603d5107c232dd5dca0665c251b39df409c3590498c4485b6df9f1d
4
+ data.tar.gz: aac47c05deecae0cfbbfbbb1888c664d305ec62434165ee89a3fb68bc6a85881
5
+ SHA512:
6
+ metadata.gz: 37359831f595b65b12f55756668023e46574626267fb16a71b459404772424f67605a4e614c570a304746d703b9f94b628d64fde64015ff6e3353a3f5469067d
7
+ data.tar.gz: eae6e737e636a325cad67d06da705cab114dbb7e4468fb7b107e3f03186c91c80748bdcf6831eac39b54b8c04e8033b0602376e698f1b255621ca5a919e3f41a
data/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # Shiplane
2
+ Convert your development docker-compose yaml files into production-ready docker deployments
3
+
4
+ ## The Mission
5
+ ### Empower Developers
6
+ Shiplane is about empowering developers to get more done, in less time, with less effort. It is intended to amplify a developer's skills and efforts for little cost. It shouldn't take hours and days to setup a working Shiplane environment. It should take minutes to get a working, sane, and relatively secure solution out of the box. Once working, everything should be tweakable as needed.
7
+
8
+ ### Platform Agnostic
9
+ Shiplane doesn't care what language you use, what orchestration tool you use, or even what kind of OS you are deploying to or from. Shiplane is merely a well-traveled, sane, and easy to use path through the Docker ecosystem that gets you from having nothing to having a working system.
10
+
11
+ ## What does Shiplane do for me?
12
+ ### Environment Consistency
13
+ Your development compose environment already defines all your application's dependencies, so why should you need to recreate your entire environment just to deploy to production? It not only duplicates work, but increases the likelihood of differences between environments that developers are always attempting to mitigate. Shiplane helps you minimize any differences so that the transition from your local, development Docker environment is just another push of code to your CI.
14
+
15
+ ### Taming the Wild West
16
+ The sheer number of Docker tools out there is staggering. While they all solve some problem, it can be difficult to decide WHICH tools you want to chain together to make your toolset as a developer new to the Docker ecosystem. Maybe you're not as big as Google and you don't need Kubernetes right now or maybe you just picked up Docker last week, but see it's potential and you're seeking the quickest path to a pipeline right now despite not understanding everything just yet. Shiplane is designed to help you move from having nothing to having a complete Docker ecosystem in a matter of minutes - not hours, days, or weeks.
17
+
18
+ ### Appropriate division of responsibilities
19
+ Docker places a lot of power in the hands of developers to define and manage their development environments and how their code is deployed. This is both good and bad. The good side of things includes developers no longer being subject to the work of other groups in your company to provision and deploy. However, the downsides include things like developers having control over platform security, the keys to the kingdom, and infrastructure. While these aren't necessarily evil, these are typically the domain of an infrastructure team to whom these responsibilities are specifically designated. How can these responsibilities be delegated appropriately without taking away the power that the Docker ecosystem provides developers? There are some existing ways to solve these problems, but they require complex systems to be setup to do so. Shiplane is DESIGNED to make this happen out of the box from day 1. It is designed specifically to allow infrastructure teams to be inserted into the process before developers do any work (infrastructure/security can provide base docker images) and during the CI pipeline (so that infrastructure/security teams can manage keys). This allows you to deploy a docker ecosystem quickly and easily with Shiplane and remain confident that you start off pretty secure and are able to continue using it as your processes evolve.
20
+
21
+ ### Makes it easy to adapt and scale
22
+ Shiplane is actually designed to be easy to use early on in the process, scale with you, be easy to switch to new tools (say you want to switch from a pure Docker deployment to using Kubernetes), and yet be easy to get rid of should you decide it is no longer for you.
23
+
24
+ ### Ok, surely there has to be a catch?
25
+ #### Not really, no. Ok, one minor catch (that you can help me with by contributing!)
26
+ While Shiplane is language and platform agnostic and you will be able to deploy that Python project or your Java project, it is written in Ruby at this time and therefore requires your machine to have Ruby installed. The instructions for doing so are included below. That's it. That's the only caveat. And, if you are so inclined, I would LOVE to accept PRs to help me create a Shiplane binary or otherwise remove this dependency entirely. There is an issue open [here](https://github.com/kirillian/shiplane/issues/11) to discuss this problem and potential fixes for it.
27
+
28
+ ## Installation
29
+ ### Installing Ruby
30
+ #### TODO
31
+
32
+ ### Installing Shiplane
33
+ #### Installing locally to a project
34
+ 1. Install Bundler if you don't already have it:
35
+ ```sh
36
+ gem install bundler
37
+ ```
38
+
39
+ 2. You can install Shiplane by adding (if you already have one, skip this first step) a Gemfile with the following contents:
40
+ ```Gemfile
41
+ source 'https://rubygems.org'
42
+
43
+ ruby '2.6.2'
44
+ ```
45
+
46
+ 3. Add the following lines to your Gemfile:
47
+ ```
48
+ gem 'shiplane' require: false
49
+ gem 'shiplane_bootstrappers_chef', require: false
50
+ gem 'shiplane_deployers_capistrano_docker', require: false
51
+ ```
52
+
53
+ 4. Run bundler to install everything:
54
+ ```sh
55
+ bundle install
56
+ ```
57
+
58
+ #### Installing globally
59
+ ```sh
60
+ gem install shiplane
61
+ gem install shiplane_bootstrappers_chef
62
+ gem install shiplane_deployers_capistrano_docker
63
+ ```
64
+
65
+ ### Adding the shiplane.yml to your project folder
66
+ You will need to have a shiplane.yml file in your project's folder in order to configure shiplane.
67
+ You can generate a default version of this file via the following command:
68
+ ```sh
69
+ rake shiplane:install[<application_name>]
70
+ ```
71
+
72
+ ### Using Shiplane
73
+ #### TODO
@@ -0,0 +1,5 @@
1
+ module Capistrano
2
+ module Shiplane
3
+ VERSION = Shiplane::Version
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ require 'shiplane/host'
2
+ require 'shiplane/deploy/container_configuration'
3
+ require 'shiplane/deploy/network_configuration'
4
+
5
+ load File.expand_path('../tasks/capistrano_stubs.rake', __FILE__)
6
+ load File.expand_path('../tasks/shiplane.rake', __FILE__)
@@ -0,0 +1,122 @@
1
+ namespace :deploy do
2
+ task :finishing do
3
+ end
4
+
5
+ task :new_release_path do
6
+ end
7
+
8
+ task :check do
9
+ end
10
+
11
+ task :set_current_revision do
12
+ end
13
+ end
14
+
15
+
16
+ # deploy
17
+ # deploy:check
18
+ # deploy:check:directories
19
+ # deploy:check:linked_dirs
20
+ # deploy:check:linked_files
21
+ # deploy:check:make_linked_dirs
22
+ # deploy:cleanup
23
+ # deploy:cleanup_rollback
24
+ # deploy:finished
25
+ # deploy:finishing
26
+ # deploy:finishing_rollback
27
+ # deploy:log_revision
28
+ # deploy:published
29
+ # deploy:publishing
30
+ # deploy:revert_release
31
+ # deploy:reverted
32
+ # deploy:reverting
33
+ # deploy:rollback
34
+ # deploy:set_current_revision
35
+ # deploy:started
36
+ # deploy:starting
37
+ # deploy:symlink:linked_dirs
38
+ # deploy:symlink:linked_files
39
+ # deploy:symlink:release
40
+ # deploy:symlink:shared
41
+ # deploy:updated
42
+ # deploy:updating
43
+ # install
44
+
45
+
46
+ # task :print_config_variables do
47
+ # end
48
+
49
+ # task updating: :new_release_path do
50
+ # end
51
+
52
+ # task :reverting do
53
+ # end
54
+
55
+ # task :publishing do
56
+ # end
57
+
58
+ # task :finishing do
59
+ # end
60
+
61
+ # task :finishing_rollback do
62
+ # end
63
+
64
+ # task :finished do
65
+ # end
66
+
67
+
68
+
69
+ # namespace :check do
70
+ # task :directories do
71
+ # end
72
+
73
+ # task :linked_dirs do
74
+ # end
75
+
76
+ # task :make_linked_dirs do
77
+ # end
78
+
79
+ # task :linked_files do
80
+ # end
81
+ # end
82
+
83
+ # namespace :symlink do
84
+ # task :release do
85
+ # end
86
+
87
+ # task :shared do
88
+ # end
89
+
90
+ # task :linked_dirs do
91
+ # end
92
+
93
+ # task :linked_files do
94
+ # end
95
+ # end
96
+
97
+ # task :cleanup do
98
+ # end
99
+
100
+ # task :cleanup_rollback do
101
+ # end
102
+
103
+ # task :log_revision do
104
+ # end
105
+
106
+ # task revert_release: :rollback_release_path do
107
+ # end
108
+
109
+ # task :new_release_path do
110
+ # end
111
+
112
+ # task :rollback_release_path do
113
+ # end
114
+
115
+ task :set_current_revision do
116
+ end
117
+
118
+ # task :set_previous_revision do
119
+ # end
120
+
121
+ # task :restart
122
+ # task :failed
@@ -0,0 +1,16 @@
1
+ require 'rake'
2
+ require 'shiplane'
3
+
4
+ desc "Convert Docker Container Artifacts From Development to Production and Upload to Docker Hub"
5
+ task :shiplane, [:appname, :sha] => ['shiplane:default']
6
+
7
+ namespace :shiplane do
8
+ task :default do |t, args|
9
+ sha = `git rev-parse HEAD`.chomp
10
+ invoke "shiplane:build", sha
11
+ end
12
+
13
+ task :build, :sha do |t, args|
14
+ Shiplane::Build.build_latest!(args['sha'], fetch(:stage))
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ # Load DSL and set up stages
2
+ require 'capistrano/setup'
3
+ require 'capistrano/framework'
4
+
5
+ require 'capistrano/ssh_doctor'
6
+ require "airbrussh/capistrano"
7
+
8
+ <% gems.each do |gem| -%>
9
+ require 'capistrano/<%= gem %>'
10
+ <% end -%>
11
+
12
+ # Load custom tasks from `lib/capistrano/tasks` if you have any defined
13
+ Dir.glob('lib/capistrano/tasks/**/*.rake').each { |r| import r }
@@ -0,0 +1,132 @@
1
+ Airbrussh.configure do |config|
2
+ config.command_output = false
3
+ end
4
+
5
+ # config valid only for current version of Capistrano
6
+ lock '3.11.0'
7
+
8
+ set :application, 'podcaster'
9
+ set :repo_url, 'git@github.com:kirillian/podcaster.git'
10
+
11
+ set :deploy_to, '/var/www/battlecryforfreedom'
12
+
13
+ # Default value for :log_level is :debug
14
+ # set :log_level, :debug
15
+
16
+ # Default value for :pty is false
17
+ # set :pty, true
18
+
19
+ # Default value for :linked_files is []
20
+ set :linked_files, %w( config/database.yml config/secrets.yml .env.production CHANGELOG.md config/sidekiq_scheduler.yml )
21
+
22
+ # Default value for linked_dirs is []
23
+ set :linked_dirs, %w( log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system )
24
+
25
+ # Default value for default_env is {}
26
+ # set :default_env, { path: "/opt/ruby/bin:$PATH" }
27
+
28
+ # Default value for keep_releases is 5
29
+ # set :keep_releases, 5
30
+
31
+ set :rbenv_type, :system
32
+ set :rbenv_ruby, File.read('.ruby-version').strip
33
+
34
+ set :rbenv_prefix, "RBENV_ROOT=#{fetch(:rbenv_path)} RBENV_VERSION=#{fetch(:rbenv_ruby)} #{fetch(:rbenv_path)}/bin/rbenv exec"
35
+ set :rbenv_map_bins, %w{rake gem bundle ruby rails sidekiq sidekiqctl}
36
+ set :rbenv_roles, :app
37
+
38
+ SSHKit.config.umask = "022"
39
+
40
+ set :ssh_options, {
41
+ forward_agent: true
42
+ }
43
+
44
+ set :passenger_restart_with_touch, true
45
+
46
+ set :branch, proc { `git rev-list --max-count=1 --abbrev-commit #{ENV["REVISION"] || ENV["BRANCH"] || "master"}`.chomp }.call
47
+
48
+ set :rollbar_token, '07b2c99baf9b43b087ce0dc96957b52f'
49
+ set :rollbar_env, Proc.new { fetch :stage }
50
+ set :rollbar_role, Proc.new { :app }
51
+
52
+ set :sha, `git rev-parse HEAD`.chomp
53
+
54
+ set :shiplane_docker_registry_username, ENV['DOCKERHUB_USERNAME']
55
+ set :shiplane_docker_registry_password, ENV['DOCKERHUB_PASSWORD']
56
+
57
+ set :shiplane_networks, {
58
+ battlecryforfreedom: {
59
+ connections: [
60
+ "nginx_reverse_proxy",
61
+ "nginx-proxy-letsencrypt",
62
+ ],
63
+ }
64
+ }
65
+
66
+ set :shiplane_containers, {
67
+ app: {
68
+ alias: 'battlecryforfreedom-app',
69
+ volumes: [],
70
+ environment: [],
71
+ expose: 3000,
72
+ capistrano_role: "docker",
73
+ repo: "kirillian2/podcaster",
74
+ command: 'bin/start',
75
+ virtual_host: "battlecryforfreedom.com",
76
+ letsencrypt_email: "john.epperson@battlecryforfreedom.com",
77
+ networks: [
78
+ "battlecryforfreedom"
79
+ ],
80
+ },
81
+ sidekiq: {
82
+ alias: 'battlecryforfreedom-sidekiq',
83
+ volumes: [],
84
+ environment: [],
85
+ capistrano_role: "docker",
86
+ repo: "kirillian2/podcaster",
87
+ command: 'bin/start_sidekiq_workers',
88
+ networks: [
89
+ "battlecryforfreedom"
90
+ ],
91
+ },
92
+ redis: {
93
+ alias: 'redis',
94
+ volumes: [
95
+ "/var/lib/redis:/var/lib/redis/data",
96
+ ],
97
+ environment: [],
98
+ expose: 6379,
99
+ publish: 6379,
100
+ capistrano_role: "docker",
101
+ repo: "redis",
102
+ tag: "4.0.9-alpine",
103
+ networks: [
104
+ "battlecryforfreedom"
105
+ ],
106
+ },
107
+ postgres: {
108
+ alias: 'postgres',
109
+ volumes: [
110
+ "/var/lib/postgres/data:/var/lib/postgresql/data",
111
+ ],
112
+ expose: 5432,
113
+ publish: 5432,
114
+ capistrano_role: "docker",
115
+ repo: "postgres",
116
+ tag: "9.6",
117
+ networks: [
118
+ "battlecryforfreedom"
119
+ ],
120
+ },
121
+ }
122
+
123
+ # namespace :deploy do
124
+ # before :started, :capture_revision
125
+ # before :started, :upload_changelog
126
+
127
+ # # config/deploy.rb
128
+ # # after 'deploy:symlink:release', 'letsencrypt:register_client'
129
+ # # after 'letsencrypt:register_client', 'letsencrypt:authorize_domain'
130
+ # # after 'letsencrypt:authorize_domain', 'letsencrypt:obtain_certificate'
131
+ # # after 'letsencrypt:obtain_certificate', 'nginx:reload'
132
+ # end
@@ -0,0 +1,61 @@
1
+ # server-based syntax
2
+ # ======================
3
+ # Defines a single server with a list of roles and multiple properties.
4
+ # You can define all roles on a single server, or split them:
5
+
6
+ # server 'example.com', user: 'deploy', roles: %w{app db web}, my_property: :my_value
7
+ # server 'example.com', user: 'deploy', roles: %w{app web}, other_property: :other_value
8
+ # server 'db.example.com', user: 'deploy', roles: %w{db}
9
+
10
+ set :rails_env, 'production'
11
+
12
+ # role-based syntax
13
+ # ==================
14
+
15
+ # Defines a role with one or multiple servers. The primary server in each
16
+ # group is considered to be the first unless any hosts have the primary
17
+ # property set. Specify the username and a domain or IP for the server.
18
+ # Don't use `:all`, it's a meta role.
19
+
20
+ # role :app, %w{deploy@example.com}, my_property: :my_value
21
+ # role :web, %w{user1@primary.com user2@additional.com}, other_property: :other_value
22
+ # role :db, %w{deploy@example.com}
23
+
24
+
25
+
26
+ # Configuration
27
+ # =============
28
+ # You can set any configuration variable like in config/deploy.rb
29
+ # These variables are then only loaded and set in this stage.
30
+ # For available Capistrano configuration variables see the documentation page.
31
+ # http://capistranorb.com/documentation/getting-started/configuration/
32
+ # Feel free to add new variables to customise your setup.
33
+
34
+
35
+
36
+ # Custom SSH Options
37
+ # ==================
38
+ # You may pass any option but keep in mind that net/ssh understands a
39
+ # limited set of options, consult the Net::SSH documentation.
40
+ # http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start
41
+ #
42
+ # Global options
43
+ # --------------
44
+ # set :ssh_options, {
45
+ # keys: %w(/home/rlisowski/.ssh/id_rsa),
46
+ # forward_agent: false,
47
+ # auth_methods: %w(password)
48
+ # }
49
+ #
50
+ # The server-based syntax can be used to override options:
51
+ # ------------------------------------
52
+ # server 'example.com',
53
+ # user: 'user_name',
54
+ # roles: %w{web app},
55
+ # ssh_options: {
56
+ # user: 'user_name', # overrides user setting above
57
+ # keys: %w(/home/user_name/.ssh/id_rsa),
58
+ # forward_agent: false,
59
+ # auth_methods: %w(publickey password)
60
+ # # password: 'please use keys'
61
+ # }
@@ -0,0 +1,44 @@
1
+ # Build Production Intermediate Image
2
+ FROM base as production-intermediate
3
+
4
+ COPY --from=intermediate $APP_PATH $APP_PATH
5
+ COPY . $APP_PATH
6
+
7
+ WORKDIR $APP_PATH
8
+
9
+ ARG GITHUB_TOKEN
10
+ ENV GITHUB_TOKEN $GITHUB_TOKEN
11
+ ENV SHIPLANE building
12
+ ENV RAILS_ENV production
13
+
14
+ RUN mkdir .git && \
15
+ touch .git/config
16
+
17
+ RUN git config --global url."https://$GITHUB_TOKEN:@github.com/".insteadOf "https://github.com/"
18
+ RUN git config --global --add url."https://$GITHUB_TOKEN:@github.com/".insteadOf "ssh://git@github.com/"
19
+
20
+ RUN bundle install --deployment --jobs=4 --without development test
21
+
22
+ RUN yarn install --production
23
+
24
+ RUN rm -rf node_modules/n2-styles/test/
25
+
26
+ RUN bundle exec rake assets:precompile
27
+
28
+ RUN sed -i "s/${GITHUB_TOKEN}//" Gemfile.lock
29
+
30
+
31
+ # Build Production Image
32
+ FROM base as production
33
+
34
+ COPY --from=production-intermediate $APP_PATH $APP_PATH
35
+
36
+ WORKDIR $APP_PATH
37
+
38
+ RUN bundle config --local path vendor/bundle
39
+ RUN bundle config --local without development:test:assets
40
+
41
+ ENV RAILS_ENV production
42
+ ENV SHIPLANE running
43
+ ENV RAILS_LOG_TO_STDOUT true
44
+ ENV RAILS_SERVE_STATIC_FILES true
@@ -0,0 +1,44 @@
1
+ project:
2
+ appname: <%= app_name %>
3
+ version_control: git
4
+ # The origin url on Github
5
+ # origin: username/repo_title
6
+ bootstrap:
7
+ env_file: .env.production
8
+ chef-bootstrapper:
9
+ package_name: chefdk_3.6.57-1_amd64.deb
10
+ package_url: https://packages.chef.io/files/stable/chefdk/3.6.57/ubuntu/18.04/chefdk_3.6.57-1_amd64.deb
11
+ build:
12
+ settings_folder: .shiplane
13
+ environment_file: .env.production
14
+ compose_filepath: docker-compose.yml
15
+ dockerfile_filepath: Dockerfile
16
+ # Define the artifacts that you want built here
17
+ artifacts:
18
+ container-name:
19
+ tag: <%= app_name %>-base:latest
20
+ repo: kirillian2/<%= app_name %>
21
+ ports:
22
+ - "3000:3000"
23
+ compose:
24
+ whitelist:
25
+ - services.container-name
26
+ blacklist:
27
+ - services.container-name.tty
28
+ - services.container-name.stdin_open
29
+ - services.container-name.volumes
30
+ - services.container-name.ports
31
+ - services.container-name.depends_on
32
+ deploy:
33
+ servers:
34
+ # put the server domain or ip address here
35
+ server-url:
36
+ # Only set this flag if you need docker to run as the sudo user (default Ubuntu AMI on AWS requires this)
37
+ requires_sudo: false
38
+ # Put SSH options here
39
+ # ssh_options:
40
+ # user: a_user
41
+ # keys: "path/to/ssh/keys"
42
+ # forward_agent: true
43
+ # auth_methods:
44
+ # - publickey
@@ -0,0 +1,101 @@
1
+ require 'fileutils'
2
+ require 'dotenv'
3
+
4
+ require_relative 'checkout_artifact'
5
+ require_relative 'convert_compose_file'
6
+ require_relative 'convert_dockerfile'
7
+ require_relative 'configuration'
8
+
9
+ module Shiplane
10
+ class Build
11
+ extend Forwardable
12
+ attr_accessor :sha, :postfix, :tag_latest
13
+
14
+ delegate %i(build_config project_config) => :shiplane_config
15
+
16
+ def initialize(sha, postfix:, tag_latest: false)
17
+ @sha = sha
18
+ @tag_latest = tag_latest
19
+ @postfix = postfix
20
+
21
+ Dotenv.overload File.join(Dir.pwd, build_config.fetch('environment_file', '.env'))
22
+ end
23
+
24
+ def appname
25
+ @appname ||= project_config['appname']
26
+ end
27
+
28
+ def project_folder_name
29
+ @project_folder_name ||= "#{appname}-#{sha}"
30
+ end
31
+
32
+ def project_folder
33
+ @project_folder ||= File.join(Dir.pwd, 'docker_builds', appname, project_folder_name)
34
+ end
35
+
36
+ def shiplane_config
37
+ @shiplane_config ||= Shiplane::Configuration.new
38
+ end
39
+
40
+ def docker_config
41
+ @docker_config ||= YAML.load(File.new(File.join(project_folder, 'docker-compose.yml')))
42
+ end
43
+
44
+ def buildable_artifacts
45
+ build_config.fetch('artifacts', {})
46
+ end
47
+
48
+ def build!
49
+ unless File.exist?(File.join(project_folder, Shiplane::SHIPLANE_CONFIG_FILENAME))
50
+ Shiplane::CheckoutArtifact.checkout!(sha)
51
+ Shiplane::ConvertComposeFile.convert_output!(project_folder, sha)
52
+ end
53
+
54
+ buildable_artifacts.each do |(artifact_name, attributes)|
55
+ compose_context = docker_config.fetch('services', {}).fetch(artifact_name.to_s, {})
56
+ Shiplane::ConvertDockerfile.convert_output!(project_folder, attributes, compose_context)
57
+
58
+ FileUtils.cd project_folder do
59
+ steps(artifact_name, attributes).select{|step| step.fetch(:condition, true) }.each do |step|
60
+ puts step[:notify_before] if step.has_key? :notify_before
61
+ success = system(step[:command])
62
+ raise StepFailureException.new(step[:command], artifact_name) unless success
63
+ puts step[:notify_after] if step.has_key? :notify_after
64
+ end
65
+ end
66
+ end
67
+ rescue StepFailureException => e
68
+ puts e.message
69
+ raise if ENV['RAISE_EXCEPTIONS_ON_FAILED_BUILD']
70
+ end
71
+
72
+ def steps(artifact_name, attributes)
73
+ [
74
+ { command: "docker-compose build --no-cache #{artifact_name}", notify_before: "Building Artifact: #{artifact_name}...", notify_after: "Docker Compose Built", stop_on_failure: true },
75
+ { command: "docker tag #{attributes['repo']}:#{sha} #{attributes['repo']}:#{postfix}-#{sha}", notify_before: "Tagging Build...", stop_on_failure: true, condition: !!postfix },
76
+ { command: "docker tag #{attributes['repo']}:#{sha} #{attributes['repo']}:#{postfix}-latest", notify_before: "Tagging Build...", stop_on_failure: true, condition: !!postfix && tag_latest },
77
+ { command: "docker tag #{attributes['repo']}:#{sha} #{attributes['repo']}:latest", notify_before: "Tagging Latest Build...", stop_on_failure: true, condition: tag_latest },
78
+ { command: "echo '#{ENV['DOCKERHUB_PASSWORD']}' | docker login --username #{ENV['DOCKERHUB_USERNAME']} --password-stdin", notify_before: "Logging into DockerHub...", stop_on_failure: true },
79
+ { command: "docker push '#{attributes['repo']}:#{sha}'", notify_before: "Pushing Image", notify_after: "Completed Artifact: #{artifact_name}...", stop_on_failure: true },
80
+ { command: "docker push '#{attributes['repo']}:#{postfix}-#{sha}'", notify_before: "Pushing #{postfix} Image", notify_after: "Completed Artifact: #{artifact_name}...", stop_on_failure: true, condition: !!postfix },
81
+ { command: "docker push '#{attributes['repo']}:#{postfix}-latest'", notify_before: "Pushing Latest #{postfix} Image", notify_after: "Completed Latest Artifact: #{artifact_name}...", stop_on_failure: true, condition: !!postfix && tag_latest },
82
+ { command: "docker push '#{attributes['repo']}:latest'", notify_before: "Pushing Latest Image", notify_after: "Completed Latest Artifact: #{artifact_name}...", stop_on_failure: true, condition: tag_latest },
83
+ ]
84
+ end
85
+
86
+ def self.build!(sha, postfix = nil)
87
+ new(sha, postfix: postfix).build!
88
+ end
89
+
90
+ def self.build_latest!(sha, postfix = nil)
91
+ new(sha, postfix: postfix, tag_latest: true).build!
92
+ end
93
+ end
94
+ end
95
+
96
+ class StepFailureException < RuntimeError
97
+ def initialize(command, artifact_name)
98
+ message = "Command [#{command}] failed for artifact: #{artifact_name}" if artifact_name
99
+ super(message)
100
+ end
101
+ end