From 0eb4614a0696383e1968ec3a991a13ce59a33669 Mon Sep 17 00:00:00 2001 From: Tayler Phillips Date: Wed, 24 Sep 2025 16:29:19 -0400 Subject: [PATCH 1/5] Fix bundler discovery issue on mac --- app/models/shipit/deploy_spec/bundler_discovery.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/shipit/deploy_spec/bundler_discovery.rb b/app/models/shipit/deploy_spec/bundler_discovery.rb index 0312935bc..f9d461f28 100644 --- a/app/models/shipit/deploy_spec/bundler_discovery.rb +++ b/app/models/shipit/deploy_spec/bundler_discovery.rb @@ -35,7 +35,7 @@ def remove_ruby_version_from_gemfile # Heroku apps often specify a ruby version. if /darwin/i.match?(RUBY_PLATFORM) # OSX is nitpicky about the -i. - %q(/usr/bin/sed -i '' '/^ruby\s/d' Gemfile) + "/usr/bin/sed -i '' '/^ruby /d' Gemfile" else %q(sed -i '/^ruby\s/d' Gemfile) end From db1c81a292e261bd8b158e50ccae662a3a3ca49f Mon Sep 17 00:00:00 2001 From: Tayler Phillips Date: Wed, 24 Sep 2025 16:30:20 -0400 Subject: [PATCH 2/5] Allow post scripts to be run if a deploy fails --- app/models/shipit/deploy_spec.rb | 8 ++++ .../shipit/task_execution_strategy/default.rb | 13 +++++++ lib/shipit/command.rb | 7 ++-- lib/shipit/deploy_commands.rb | 22 ++++++++++- lib/shipit/rollback_commands.rb | 7 ++++ test/models/deploy_spec_test.rb | 20 ++++++++++ test/unit/command_test.rb | 15 ++++++++ test/unit/deploy_commands_test.rb | 37 +++++++++++++++++++ test/unit/rollback_commands_test.rb | 17 +++++++++ 9 files changed, 142 insertions(+), 4 deletions(-) diff --git a/app/models/shipit/deploy_spec.rb b/app/models/shipit/deploy_spec.rb index a2faf19a1..23e2ab0af 100644 --- a/app/models/shipit/deploy_spec.rb +++ b/app/models/shipit/deploy_spec.rb @@ -117,6 +117,10 @@ def deploy_steps! deploy_steps || cant_detect!(:deploy) end + def deploy_post + config('deploy', 'post') { [] } + end + def deploy_variables Array.wrap(config('deploy', 'variables')).map(&VariableDefinition.method(:new)) end @@ -143,6 +147,10 @@ def retries_on_rollback config('rollback', 'retries') { nil } end + def rollback_post + config('rollback', 'post') { [] } + end + def fetch_deployed_revision_steps config('fetch') || discover_fetch_deployed_revision_steps end diff --git a/app/models/shipit/task_execution_strategy/default.rb b/app/models/shipit/task_execution_strategy/default.rb index 0b39ef0a9..bf583de1b 100644 --- a/app/models/shipit/task_execution_strategy/default.rb +++ b/app/models/shipit/task_execution_strategy/default.rb @@ -24,13 +24,17 @@ def run rescue Command::TimedOut => e @task.write("\n#{e.message}\n") @task.report_timeout!(e) + failure!(e) rescue Command::Error => e @task.write("\n#{e.message}\n") @task.report_failure!(e) + failure!(e) rescue StandardError => e @task.report_error!(e) + failure!(e) rescue Exception => e @task.report_error!(e) + failure!(e) raise end @@ -104,6 +108,15 @@ def capture(command) rescue Command::Error false end + + def failure!(error) + return unless @commands.respond_to?(:failed!) + + @commands.failed!(error.message) + return unless @commands.respond_to?(:failure_step) && @commands.failure_step + + capture!(@commands.failure_step) + end end end end diff --git a/lib/shipit/command.rb b/lib/shipit/command.rb index 6f5eb7455..b6ae8c4c2 100644 --- a/lib/shipit/command.rb +++ b/lib/shipit/command.rb @@ -26,11 +26,12 @@ def initialize(message, exit_code) end end - attr_reader :out, :chdir, :env, :args, :pid, :timeout + attr_reader :out, :chdir, :env, :args, :pid, :timeout, :run_on_error def initialize(*args, chdir:, default_timeout: Shipit.default_inactivity_timeout, env: {}) - @args, options = parse_arguments(args) - @timeout = parse_timeout(options['timeout'] || options[:timeout]) || default_timeout + @args, @options = parse_arguments(args) + @timeout = parse_timeout(@options['timeout'] || @options[:timeout]) || default_timeout + @run_on_error = (@options['on_error'] || @options[:on_error]) == true @env = env.transform_values { |v| v&.to_s } @chdir = chdir.to_s @timed_out = false diff --git a/lib/shipit/deploy_commands.rb b/lib/shipit/deploy_commands.rb index aadb09486..700eba036 100644 --- a/lib/shipit/deploy_commands.rb +++ b/lib/shipit/deploy_commands.rb @@ -2,16 +2,36 @@ module Shipit class DeployCommands < TaskCommands + def initialize(task) + super + @failed = false + @error_message = nil + end + def steps deploy_spec.deploy_steps! end + def failure_step + return unless deploy_spec.deploy_post.present? + + command = Command.new(deploy_spec.deploy_post, env:, chdir: steps_directory) + command if command.run_on_error + end + + def failed!(error_message = nil) + @failed = true + @error_message = error_message + end + def env commit = @task.until_commit super.merge( 'SHA' => commit.sha, 'REVISION' => commit.sha, - 'DIFF_LINK' => diff_url + 'DIFF_LINK' => diff_url, + 'FAILED' => @failed ? '1' : '0', + 'FAILURE_MESSAGE' => @error_message.to_s ) end diff --git a/lib/shipit/rollback_commands.rb b/lib/shipit/rollback_commands.rb index e22cc1b53..0239668a9 100644 --- a/lib/shipit/rollback_commands.rb +++ b/lib/shipit/rollback_commands.rb @@ -6,6 +6,13 @@ def steps deploy_spec.rollback_steps! end + def failure_step + return unless deploy_spec.rollback_post.present? + + command = Command.new(deploy_spec.rollback_post, env:, chdir: steps_directory) + command if command.run_on_error + end + def env super.merge( 'ROLLBACK' => '1' diff --git a/test/models/deploy_spec_test.rb b/test/models/deploy_spec_test.rb index fbda048f4..f73e942b7 100644 --- a/test/models/deploy_spec_test.rb +++ b/test/models/deploy_spec_test.rb @@ -183,6 +183,16 @@ class DeploySpecTest < ActiveSupport::TestCase end end + test '#deploy_post returns a default value if `deploy.post` is not present' do + @spec.stubs(:load_config).returns('deploy' => {}) + assert_equal [], @spec.deploy_post + end + + test '#deploy_post returns `deploy.post` if present' do + @spec.stubs(:load_config).returns('deploy' => { 'post' => %w[foo bar baz] }) + assert_equal %w[foo bar baz], @spec.deploy_post + end + test '#retries_on_deploy returns `deploy.retries` if present' do @spec.stubs(:load_config).returns('deploy' => { 'retries' => 5 }) assert_equal 5, @spec.retries_on_deploy @@ -208,6 +218,16 @@ class DeploySpecTest < ActiveSupport::TestCase assert_nil @spec.retries_on_rollback end + test '#rollback_post returns a default value if `rollback.post` is not present' do + @spec.stubs(:load_config).returns('rollback' => {}) + assert_equal [], @spec.rollback_post + end + + test '#rollback_post returns `rollback.post` if present' do + @spec.stubs(:load_config).returns('rollback' => { 'post' => %w[foo bar baz] }) + assert_equal %w[foo bar baz], @spec.rollback_post + end + test '#rollback_steps returns `cap $ENVIRONMENT deploy:rollback` if a `Capfile` is present' do @spec.expects(:bundler?).returns(true).at_least_once @spec.expects(:capistrano?).returns(true) diff --git a/test/unit/command_test.rb b/test/unit/command_test.rb index 6597e0c32..19d5f5b21 100644 --- a/test/unit/command_test.rb +++ b/test/unit/command_test.rb @@ -124,6 +124,21 @@ class CommandTest < ActiveSupport::TestCase assert_equal 'timed out and terminated with INT signal', command.termination_status end + test 'on_error defaults to false' do + command = Command.new('cap $LANG deploy', chdir: '.') + assert_equal false, command.run_on_error + end + + test 'accepts option for on_error behavior with symbol key' do + command = Command.new({ 'cap $LANG deploy' => { on_error: true } }, chdir: '.') + assert_equal true, command.run_on_error + end + + test 'accepts option for on_error behavior with string key' do + command = Command.new({ 'cap $LANG deploy' => { 'on_error' => true } }, chdir: '.') + assert_equal true, command.run_on_error + end + private def command_signaller_thread(command, signal: 'KILL') diff --git a/test/unit/deploy_commands_test.rb b/test/unit/deploy_commands_test.rb index 5fe8406c3..9c211f20e 100644 --- a/test/unit/deploy_commands_test.rb +++ b/test/unit/deploy_commands_test.rb @@ -307,6 +307,20 @@ def setup assert_equal @deploy.id.to_s, command.env['TASK_ID'] end + test "#perform calls cap $environment deploy with the FAILED state in the environment" do + commands = @commands.perform + assert_equal 1, commands.length + command = commands.first + assert_equal '0', command.env['FAILED'] + end + + test "#perform calls cap $environment deploy with the FAILURE_MESSAGE in the environment" do + commands = @commands.perform + assert_equal 1, commands.length + command = commands.first + assert_equal '', command.env['FAILURE_MESSAGE'] + end + test "#perform transliterates the user name" do @deploy.user = User.new(login: 'Sirupsen', name: "Simon Hørup Eskildsen") commands = @commands.perform @@ -390,5 +404,28 @@ def setup FileUtils.expects(:rm_rf).never @commands.clear_working_directory end + + test "failure_step returns nil if there's no deploy_post step" do + @deploy_spec.stubs(:deploy_post).returns(nil) + assert_nil @commands.failure_step + end + + test "failure_step returns a Command if there's a deploy_post with config to run on_error" do + @deploy_spec.stubs(:deploy_post).returns('echo "hello"' => { 'on_error' => true }) + command = @commands.failure_step + assert_instance_of Command, command + assert_equal ['echo "hello"'], command.args + end + + test "failure_step returns nil if there's no deploy_post without config to run on_error" do + @deploy_spec.stubs(:deploy_post).returns('echo "hello"') + assert_nil @commands.failure_step + end + + test "#failed! updates the env with the FAILED and FAILURE_MESSAGE states" do + @commands.failed!('Deployment failed') + assert_equal '1', @commands.env['FAILED'] + assert_equal 'Deployment failed', @commands.env['FAILURE_MESSAGE'] + end end end diff --git a/test/unit/rollback_commands_test.rb b/test/unit/rollback_commands_test.rb index 3092e5425..3c4441718 100644 --- a/test/unit/rollback_commands_test.rb +++ b/test/unit/rollback_commands_test.rb @@ -33,5 +33,22 @@ def setup command = commands.first assert_equal '1', command.env['ROLLBACK'] end + + test "failure_step returns nil if there's no rollback_post step" do + @deploy_spec.stubs(:rollback_post).returns(nil) + assert_nil @commands.failure_step + end + + test "failure_step returns a Command if there's a rollback_post with config to run on_error" do + @deploy_spec.stubs(:rollback_post).returns('echo "hello"' => { 'on_error' => true }) + command = @commands.failure_step + assert_instance_of Command, command + assert_equal ['echo "hello"'], command.args + end + + test "failure_step returns nil if there's a rollback_post without config to run on_error" do + @deploy_spec.stubs(:rollback_post).returns('echo "hello"') + assert_nil @commands.failure_step + end end end From 6a5dc942946023603e920d3218f619ae55c481d0 Mon Sep 17 00:00:00 2001 From: Tayler Phillips Date: Wed, 24 Sep 2025 16:31:19 -0400 Subject: [PATCH 3/5] Update readme with new config for post scripts Converted html to pure markdown. Added bundle frozen config. General cleanup of the readme. --- README.md | 294 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 163 insertions(+), 131 deletions(-) diff --git a/README.md b/README.md index 7ef1214bd..ef3f2fd69 100644 --- a/README.md +++ b/README.md @@ -4,73 +4,73 @@ **Shipit** is a deployment tool that makes shipping code better for everyone. It's especially great for large teams of developers and designers who work together to build and deploy GitHub repos. You can use it to: -* add new applications to your deployment environment without having to change core configuration files repeatedly — `shipit.yml` is basically plug and play -* control the pace of development by pushing, locking, and rolling back deploys from within Shipit -* enforce checklists and provide monitoring right at the point of deployment. +* Add new applications to your deployment environment without having to change core configuration files repeatedly — `shipit.yml` is basically plug and play +* Control the pace of development by pushing, locking, and rolling back deploys from within Shipit +* Enforce checklists and provide monitoring right at the point of deployment. Shipit is compatible with just about anything that you can deploy using a script. It natively detects stacks using [bundler](http://bundler.io/) and [Capistrano](http://capistranorb.com/), and it has tools that make it easy to deploy to [Heroku](https://www.heroku.com/) or [RubyGems](https://rubygems.org/). At Shopify, we've used Shipit to synchronize and deploy hundreds of projects across dozens of teams, using Python, Rails, RubyGems, Java, and Go. This guide aims to help you [set up](#installation-and-setup), [use](#using-shipit), and [understand](#reference) Shipit. -*Shipit requires a database (MySQL, PostgreSQL or SQLite3), redis, and Ruby 2.6 or superior.* +*Shipit requires a database (MySQL, PostgreSQL or SQLite3), Redis, and Ruby 2.6 or superior.* * * * -

Table of contents

+## Table of contents -**I. INSTALLATION & SETUP** +### I. INSTALLATION & SETUP * [Installation](#installation) * [Updating an existing installation](#updating-shipit) -**II. USING SHIPIT** +### II. USING SHIPIT * [Adding stacks](#adding-stacks) * [Working on stacks](#working-on-stacks) * [Configuring stacks](#configuring-stacks) -**III. REFERENCE** +### III. REFERENCE * [Format and content of shipit.yml](#configuring-shipit) * [Script parameters](#script-parameters) * [Configuring providers](#configuring-providers) * [Free samples](/examples/shipit.yml) -**IV. INTEGRATING** +### IV. INTEGRATING * [Registering webhooks](#integrating-webhooks) -**V. CONTRIBUTING** +### V. CONTRIBUTING * [Instructions](#contributing-instructions) * [Local development](#contributing-local-dev) * * * -

I. INSTALLATION & SETUP

+## I. INSTALLATION & SETUP {#installation-and-setup} -

Installation

+### Installation To create a new Shipit installation you can follow the [setup guide](docs/setup.md). -

Updating an existing installation

+### Updating an existing installation {#updating-shipit} 1. If you locked the gem to a specific version in your Gemfile, update it there. 2. Update the `shipit-engine` gem with `bundle update shipit-engine`. 3. Install new migrations with `rake shipit:install:migrations db:migrate`. -

Specific updates requiring more steps

+### Specific updates requiring more steps {#special-update} If you are upgrading from `0.21` or older, you will have to update the configuration. Please follow [the dedicated upgrade guide](docs/updates/0.22.md) * * * -

II. USING SHIPIT

+## II. USING SHIPIT {#using-shipit} The main workflows in Shipit are [adding stacks](#adding-stacks), [working on stacks](#working-on-stacks), and [configuring stacks](#configuring-stacks). A **stack** is composed of a GitHub repository, a branch, and a deployment environment. Shipit tracks the commits made to the branch, and then displays them in the stack overview. From there, you can deploy the branch to whatever environment you've chosen (some typical environments include *production*, *staging*, *performance*, etc.). -

Add a new stack

+### Add a new stack {#adding-stacks} 1. From the main page in Shipit, click **Add a stack**. 2. On the **Create a stack** page, enter the required information: @@ -80,7 +80,7 @@ A **stack** is composed of a GitHub repository, a branch, and a deployment envir * Deploy URL 3. When you're finished, click **Create stack**. -

Work on an existing stack

+### Work on an existing stack {#working-on-stacks} 1. If you want to browse the list of available stacks, click **Show all stacks** on the main page in Shipit. If you know the name of the stack you're looking for, enter it in the search field. 2. Click the name of the stack you want to open. @@ -93,9 +93,7 @@ A **stack** is composed of a GitHub repository, a branch, and a deployment envir 4. When you're ready to deploy an undeployed commit, click the relevant **Deploy** button on the stack's overview page. 5. From the **Deploy** page, complete the checklist, then click **Create deploy**. - - -

Edit stack settings

+### Edit stack settings {#configuring-stacks} To edit a stack's settings, open the stack in Shipit, then click the gear icon in the page header. @@ -109,9 +107,9 @@ From a stack's **Settings** page, you can: * * * -

III. REFERENCE

+## III. REFERENCE {#reference} -

Configuring shipit.yml

+### Configuring `shipit.yml` {#configuring-shipit} The settings in the `shipit.yml` file relate to the different things you can do with Shipit: @@ -137,36 +135,36 @@ Lastly, if you override the `app_name` configuration in your Shipit deployment, * * * -

Respecting bare shipit.yml files

+### Respecting bare `shipit.yml` files {#respecting-bare-files} -Shipit will, by default, respect the "bare" shipit.yml file as a fallback option if no more specifically-named file exists (such as shipit.staging.yml). +Shipit will, by default, respect the "bare" `shipit.yml` file as a fallback option if no more specifically-named file exists (such as `shipit.staging.yml`). -You can configure this behavior via the attribute Shipit.respect_bare_shipit_file. +You can configure this behaviour via the attribute `Shipit.respect_bare_shipit_file`. -- The value false will disable this behavior and instead cause Shipit to emit an error upon deploy if Shipit cannot find a more specifically-named file. -- Setting this attribute to any other value (**including nil**), or not setting this attribute, will cause Shipit to use the default behavior of respecting bare shipit.yml files. +* The value `false` will disable this behaviour and instead cause Shipit to emit an error upon deploy if Shipit cannot find a more specifically-named file. +* Setting this attribute to any other value (**including `nil`**), or not setting this attribute, will cause Shipit to use the default behaviour of respecting bare `shipit.yml` files. -You can determine if Shipit is configured to respect bare files using Shipit.respect_bare_shipit_file?. +You can determine if Shipit is configured to respect bare files using `Shipit.respect_bare_shipit_file?`. * * * -

Installing dependencies

+### Installing dependencies -The **dependencies** step allows you to install all the packages your deploy script needs. +The **`dependencies`** step allows you to install all the packages your deploy script needs. -

Bundler

+#### Bundler {#bundler-support} If your application uses Bundler, Shipit will detect it automatically and take care of the `bundle install` and prefix your commands with `bundle exec`. By default, the following gem groups will be ignored: - - `default` - - `production` - - `development` - - `test` - - `staging` - - `benchmark` - - `debug` + * `default` + * `production` + * `development` + * `test` + * `staging` + * `benchmark` + * `debug` The gems you need in order to deploy should be in a different group, such as `deploy`. @@ -181,7 +179,7 @@ dependencies: - debug ``` -

Other dependencies

+#### Other dependencies If your deploy script uses another tool to install dependencies, you can install them manually via `dependencies.override`: @@ -191,7 +189,8 @@ dependencies: - npm install ``` -**dependencies.pre** If you wish to execute commands before Shipit installs the dependencies, you can specify them here. + +**`dependencies.pre`** If you wish to execute commands before Shipit installs the dependencies, you can specify them here. For example: @@ -201,9 +200,20 @@ dependencies: - mkdir tmp/ - cp -R /var/cache/ tmp/cache ``` -
-**dependencies.post** If you wish to execute commands after Shipit installed the dependencies, you can specify them here: + +**`dependencies.bundler.frozen`** If you wish to execute your bundle, for the **deploy** group, without the frozen flag: + +For example: + +```yml +dependencies: + bundler: + frozen: false +``` + + +**`dependencies.post`** If you wish to execute commands after Shipit installed the dependencies, you can specify them here: For example: @@ -212,14 +222,12 @@ dependencies: post: - cp -R tmp/cache /var/cache/ ``` -
- -

Deployment

+### Deployment The `deploy` and `rollback` sections are the core of Shipit: -**deploy.override** contains an array of the shell commands required to deploy the application. Shipit will try to infer it from the repository structure, but you can change the default inference. +**`deploy.override`** contains an array of the shell commands required to deploy the application. Shipit will try to infer it from the repository structure, but you can change the default inference. For example: @@ -228,9 +236,9 @@ deploy: override: - ./script/deploy ``` -
-**deploy.pre** If you wish to execute commands before Shipit executes your deploy script, you can specify them here. + +**`deploy.pre`** If you wish to execute commands before Shipit executes your deploy script, you can specify them here. For example: @@ -239,9 +247,9 @@ deploy: pre: - ./script/notify_deploy_start ``` -
-**deploy.post** If you wish to execute commands after Shipit executed your deploy script, you can specify them here. + +**`deploy.post`** If you wish to execute commands after Shipit executed your deploy script, you can specify them here. For example: @@ -250,12 +258,31 @@ deploy: post: - ./script/notify_deploy_end ``` -
+ + +If you would like the post script to run even on error, you can pass the following option: + +```yml +deploy: + post: + - ./script/notify_deploy_end: { on_error: true } +``` + +Or + +```yml +deploy: + post: + - ./script/notify_deploy_end: + on_error: true +``` + +This option can come in handy when you are tracking deployment externally. The default behaviour will not run the post script. You can also accept custom environment variables defined by the user that triggers the deploy: -**deploy.variables** contains an array of variable definitions. +**`deploy.variables`** contains an array of variable definitions. For example: @@ -267,9 +294,9 @@ deploy: title: Run database migrations on deploy default: 1 ``` -
-**deploy.variables.select** will turn the input into a `` of values. For example: @@ -284,14 +311,14 @@ deploy: - west - north ``` -
-**deploy.max_commits** defines the maximum number of commits that should be shipped per deploy. Defaults to `8` if no value is provided. -To disable this limit, you can use use an explicit null value: `max_commits: null`. Continuous Delivery will then deploy any number of commits. +**`deploy.max_commits`** defines the maximum number of commits that should be shipped per deploy. Defaults to `8` if no value is provided. + +To disable this limit, you can use an explicit null value: `max_commits: null`. Continuous Delivery will then deploy any number of commits. Human users will be warned that they are not respecting the recommendation, but allowed to continue. -However continuous delivery will respect this limit. If there is no deployable commits in this range, a human intervention will be required. +However, continuous delivery will respect this limit. If there is no deployable commits in this range, a human intervention will be required. For example: @@ -299,9 +326,9 @@ For example: deploy: max_commits: 5 ``` -
-**deploy.interval** defines the interval between the end of a deploy and the next deploy, when continuous delivery is enabled. You can use s, m, h, d as units for seconds, minutes, hours, and days. Defaults to 0, which means a new deploy will start as soon as the current one finishes. + +**`deploy.interval`** defines the interval between the end of a deploy and the next deploy, when continuous delivery is enabled. You can use s, m, h, d as units for seconds, minutes, hours, and days. Defaults to 0, which means a new deploy will start as soon as the current one finishes. For example, this will wait 5 minutes after the end of a deploy before starting a new one: @@ -310,7 +337,8 @@ deploy: interval: 5m ``` -**deploy.retries** enables retries for a stack, and defines the maximum amount of times that Shipit will retry a deploy that finished with a `failed`, `error` or `timedout` status. + +**`deploy.retries`** enables retries for a stack, and defines the maximum amount of times that Shipit will retry a deploy that finished with a `failed`, `error` or `timedout` status. For example, this will retry a deploy twice if it fails. @@ -319,7 +347,8 @@ deploy: retries: 2 ``` -**rollback.override** contains an array of the shell commands required to rollback the application to a previous state. Shipit will try to infer it from the repository structure, but you can change the default inference. This key defaults to `disabled` unless Capistrano is detected. + +**`rollback.override`** contains an array of the shell commands required to rollback the application to a previous state. Shipit will try to infer it from the repository structure, but you can change the default inference. This key defaults to `disabled` unless Capistrano is detected. For example: @@ -328,9 +357,9 @@ rollback: override: - ./script/rollback ``` -
-**rollback.pre** If you wish to execute commands before Shipit executes your rollback script, you can specify them here: + +**`rollback.pre`** If you wish to execute commands before Shipit executes your rollback script, you can specify them here: For example: @@ -339,9 +368,9 @@ rollback: pre: - ./script/notify_rollback_start ``` -
-**rollback.post** If you wish to execute commands after Shipit executed your rollback script, you can specify them here: + +**`rollback.post`** If you wish to execute commands after Shipit executed your rollback script, you can specify them here: For example: @@ -350,10 +379,9 @@ rollback: post: - ./script/notify_rollback_end ``` -
-**fetch** contains an array of the shell commands that Shipit executes to check the revision of the currently-deployed version. This key defaults to `disabled`. +**`fetch`** contains an array of the shell commands that Shipit executes to check the revision of the currently-deployed version. This key defaults to `disabled`. For example: ```yml @@ -361,13 +389,14 @@ fetch: curl --silent https://app.example.com/services/ping/version ``` -**Note:** Currently, deployments in emergency mode are configured to occur concurrently via [the `build_deploy` method](https://github.com/Shopify/shipit-engine/blob/main/app/models/shipit/stack.rb), -whose `allow_concurrency` keyword argument defaults to `force`, where `force` is true when emergency mode is enabled. -If you'd like to separate these two from one another, override this method as desired in your service. +> [!NOTE] +> Currently, deployments in emergency mode are configured to occur concurrently via [the `build_deploy` method](https://github.com/Shopify/shipit-engine/blob/main/app/models/shipit/stack.rb), +> whose `allow_concurrency` keyword argument defaults to `force`, where `force` is true when emergency mode is enabled. +> If you'd like to separate these two from one another, override this method as desired in your service. -

Kubernetes

+### Kubernetes -**kubernetes** allows to specify a Kubernetes namespace and context to deploy to. +**`kubernetes`** allows to specify a Kubernetes namespace and context to deploy to. For example: ```yml @@ -376,11 +405,11 @@ kubernetes: context: tier4 ``` -**kubernetes.template_dir** allows to specify a Kubernetes template directory. It defaults to `./config/deploy/$ENVIRONMENT` +**`kubernetes.template_dir`** allows to specify a Kubernetes template directory. It defaults to `./config/deploy/$ENVIRONMENT` -

Environment

+### Environment -**machine.environment** contains the extra environment variables that you want to provide during task execution. +**`machine.environment`** contains the extra environment variables that you want to provide during task execution. For example: ```yml @@ -389,9 +418,9 @@ machine: key: val # things added as environment variables ``` -

Directory

+### Directory -**machine.directory** specifies a subfolder in which to execute all tasks. Useful for repositories containing multiple applications or if you don't want your deploy scripts to be located at the root. +**`machine.directory`** specifies a subfolder in which to execute all tasks. Useful for repositories containing multiple applications or if you don't want your deploy scripts to be located at the root. For example: ```yml @@ -399,9 +428,9 @@ machine: directory: scripts/deploy/ ``` -

Cleanup

+### Cleanup -**machine.cleanup** specifies whether or not the deploy working directory should be cleaned up once the deploy completed. Defaults to `true`, but can be useful to disable temporarily to investigate bugs. +**`machine.cleanup`** specifies whether or not the deploy working directory should be cleaned up once the deploy completed. Defaults to `true`, but can be useful to disable temporarily to investigate bugs. For example: ```yml @@ -409,9 +438,9 @@ machine: cleanup: false ``` -

CI

+### CI -**ci.require** contains an array of the [statuses context](https://docs.github.com/en/rest/reference/commits#commit-statuses) you want Shipit to disallow deploys if any of them is missing on the commit being deployed. +**`ci.require`** contains an array of the [statuses context](https://docs.github.com/en/rest/reference/commits#commit-statuses) you want Shipit to disallow deploys if any of them is missing on the commit being deployed. For example: ```yml @@ -420,7 +449,7 @@ ci: - ci/circleci ``` -**ci.hide** contains an array of the [statuses context](https://docs.github.com/en/rest/reference/commits#commit-statuses) you want Shipit to ignore. +**`ci.hide`** contains an array of the [statuses context](https://docs.github.com/en/rest/reference/commits#commit-statuses) you want Shipit to ignore. For example: ```yml @@ -429,7 +458,7 @@ ci: - ci/circleci ``` -**ci.allow_failures** contains an array of the [statuses context](https://docs.github.com/en/rest/reference/commits#commit-statuses) you want to be visible but not to required for deploy. +**`ci.allow_failures`** contains an array of the [statuses context](https://docs.github.com/en/rest/reference/commits#commit-statuses) you want to be visible but not to required for deploy. For example: ```yml @@ -438,7 +467,7 @@ ci: - ci/circleci ``` -**ci.blocking** contains an array of the [statuses context](https://docs.github.com/en/rest/reference/commits#commit-statuses) you want to disallow deploys if any of them is missing or failing on any of the commits being deployed. +**`ci.blocking`** contains an array of the [statuses context](https://docs.github.com/en/rest/reference/commits#commit-statuses) you want to disallow deploys if any of them is missing or failing on any of the commits being deployed. For example: ```yml @@ -447,13 +476,13 @@ ci: - soc/compliance ``` -

Merge Queue

+### Merge Queue -The merge queue allows developers to register pull requests which will be merged by Shipit once the stack is clear (no lock, no failing CI, no backlog). It can be enabled on a per stack basis via the settings page. +The merge queue allows developers to register pull requests which will be merged by Shipit once the stack is clear (no lock, no failing CI, no backlog). It can be enabled on a per-stack basis via the settings page. It can be customized via several `shipit.yml` properties: -**merge.revalidate_after** a duration after which pull requests that couldn't be merged are rejected from the queue. Defaults to unlimited. +**`merge.revalidate_after`** a duration after which pull requests that couldn't be merged are rejected from the queue. Defaults to unlimited. For example: ```yml @@ -461,7 +490,7 @@ merge: revalidate_after: 12m30s ``` -**merge.require** contains an array of the [statuses context](https://docs.github.com/en/rest/reference/commits#commit-statuses) that you want Shipit to consider as failing if they aren't present on the pull request. Defaults to `ci.require` if present, or empty otherwise. +**`merge.require`** contains an array of the [statuses context](https://docs.github.com/en/rest/reference/commits#commit-statuses) that you want Shipit to consider as failing if they aren't present on the pull request. Defaults to `ci.require` if present, or empty otherwise. For example: ```yml @@ -470,7 +499,7 @@ merge: - continuous-integration/travis-ci/push ``` -**merge.ignore** contains an array of the [statuses context](https://docs.github.com/en/rest/reference/commits#commit-statuses) that you want Shipit not to consider when merging pull requests. Defaults to the union of `ci.allow_failures` and `ci.hide` if any is present or empty otherwise. +**`merge.ignore`** contains an array of the [statuses context](https://docs.github.com/en/rest/reference/commits#commit-statuses) that you want Shipit not to consider when merging pull requests. Defaults to the union of `ci.allow_failures` and `ci.hide` if any is present or empty otherwise. For example: ```yml @@ -479,7 +508,7 @@ merge: - codeclimate ``` -**merge.method** the [merge method](https://docs.github.com/en/rest/reference/pulls#merge-a-pull-request--parameters) to use for this stack. If it's not set the default merge method will be used. Can be either `merge`, `squash` or `rebase`. +**`merge.method`** the [merge method](https://docs.github.com/en/rest/reference/pulls#merge-a-pull-request--parameters) to use for this stack. If it's not set the default merge method will be used. Can be either `merge`, `squash` or `rebase`. For example: ```yml @@ -487,7 +516,7 @@ merge: method: squash ``` -**merge.max_divergence.commits** the maximum number of commits a pull request can be behind its merge base, after which pull requests are rejected from the merge queue. +**`merge.max_divergence.commits`** the maximum number of commits a pull request can be behind it's merge base, after which pull requests are rejected from the merge queue. For example: ```yml @@ -496,7 +525,7 @@ merge: commits: 50 ``` -**merge.max_divergence.age** a duration after the commit date of the merge base, after which pull requests will be rejected from the merge queue. +**`merge.max_divergence.age`** a duration after the commit date of the merge base, after which pull requests will be rejected from the merge queue. For example: ```yml @@ -505,11 +534,11 @@ merge: age: 72h ``` -

Custom tasks

+### Custom tasks You can create custom tasks that users execute directly from a stack's overview page in Shipit. To create a new custom task, specify its parameters in the `tasks` section of the `shipit.yml` file. For example: -**tasks.restart** restarts the application. +**`tasks.restart`** restarts the application. ```yml tasks: @@ -520,7 +549,7 @@ tasks: - ssh deploy@myserver.example.com 'touch myapp/restart.txt' ``` -By default, custom tasks are not allowed to be triggered while a deploy is running. But if it's safe for that specific task, you can change that behavior with the `allow_concurrency` attribute: +By default, custom tasks are not allowed to be triggered while a deploy is running. But if it's safe for that specific task, you can change that behaviour with the `allow_concurrency` attribute: ```yml tasks: @@ -560,11 +589,11 @@ tasks: - name: POD_ID ``` - +### Custom Links -You can add custom links to the header of a stacks overview page in Shipit. To create a new custom link, specify its parameters in the links section of the shipit.yml file. The link title is a humanized version of the key. For example: +You can add custom links to the header of a stacks overview page in Shipit. To create a new custom link, specify its parameters in the `links` section of the shipit.yml file. The link title is a humanized version of the key. For example: -**links.monitoring_dashboard** creates a link in the header of of the page titled "Monitoring dashboard" +**`links.monitoring_dashboard`** creates a link in the header of the page titled "Monitoring dashboard" You can specify multiple custom links: @@ -574,11 +603,11 @@ links: other_link: https://example.com/something_else.html ``` -

Review process

+### Review process You can display review elements, such as monitoring data or a pre-deployment checklist, on the deployment page in Shipit: -**review.checklist** contains a pre-deploy checklist that appears on the deployment page in Shipit, with each item in the checklist as a separate string in the array. It can contain `strong` and `a` HTML tags. Users cannot deploy from Shipit until they have checked each item in the checklist. +**`review.checklist`** contains a pre-deploy checklist that appears on the deployment page in Shipit, with each item in the checklist as a separate string in the array. It can contain `strong` and `a` HTML tags. Users cannot deploy from Shipit until they have checked each item in the checklist. For example: @@ -590,9 +619,8 @@ review: - Has the Docs team been notified of any major changes to the app? - Is the app stable right now? ``` -
-**review.monitoring** contains a list of inclusions that appear on the deployment page in Shipit. Inclusions can either be images or iframes. +**`review.monitoring`** contains a list of inclusions that appear on the deployment page in Shipit. Inclusions can either be images or iframes. For example: @@ -603,9 +631,7 @@ review: - iframe: https://example.com/monitoring.html ``` -
- -**review.checks** contains a list of commands that will be executed during the pre-deploy review step. +**`review.checks`** contains a list of commands that will be executed during the pre-deploy review step. Their output appears on the deployment page in Shipit, and if continuous delivery is enabled, deploys will only be triggered if those commands are successful. For example: @@ -616,7 +642,7 @@ review: - bundle exec rake db:migrate:status ``` -

Inherit From

+### Inherit From If the `inherit_from` key is specified, a deep-merge will be performed on the file therein, overwriting any duplicated values from the parent. Keys may be chained across files. Example: @@ -647,12 +673,12 @@ machine: PUBLIC: false ``` -Loading 'shipit.production.yml' would result in: +Loading `shipit.production.yml` would result in: ```rb {"machine"=>{"environment"=>{"TEST"=>true, "PUBLIC"=>true}}, "deploy"=>{"override"=>["./some_deployment_process.sh ${PUBLIC}"]}} ``` -

Shell commands timeout

+### Shell commands timeout All the shell commands can take an optional `timeout` parameter. This is the value in seconds that a command can be inactive before Shipit will terminate the task. @@ -674,7 +700,7 @@ See also `commands_inactivity_timeout` in `secrets.yml` for a global timeout set *** -

Script parameters

+## Script parameters Your deploy scripts have access to the following environment variables: @@ -684,8 +710,8 @@ Your deploy scripts have access to the following environment variables: * `GITHUB_REPO_NAME`: Name of the GitHub repository being used for the current deploy/task. * `GITHUB_REPO_OWNER`: The GitHub username of the repository owner for the current deploy/task. * `EMAIL`: Email of the user that triggered the deploy/task (if available) -* `ENVIRONMENT`: The stack environment (e.g `production` / `staging`) -* `BRANCH`: The stack branch (e.g `main`) +* `ENVIRONMENT`: The stack environment (e.g. `production` / `staging`) +* `BRANCH`: The stack branch (e.g. `main`) * `LAST_DEPLOYED_SHA`: The git SHA of the last deployed commit * `DIFF_LINK`: URL to the diff on GitHub. * `TASK_ID`: ID of the task that is running @@ -696,28 +722,34 @@ These variables are accessible only during deploys and rollback: * `REVISION`: the git SHA of the revision that must be deployed in production * `SHA`: alias for REVISION +* `FAILED`: Set to `1` or `0` to show the state of the task +* `ERROR_MESSAGE`: Contains the error when a deploy/rollback fails + +The following is available when a rollback is triggered: -

Configuring providers

+* `ROLLBACK`: set to `1` + +## Configuring providers ### Heroku To use Heroku integration (`lib/snippets/push-to-heroku`), make sure that the environment has [Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli) available. -### Kubernetes +### Kubernetes {#configuring-kubernetes} For Kubernetes, you have to provision Shipit environment with the following tools: * `kubectl` * `kubernetes-deploy` [gem](https://github.com/Shopify/kubernetes-deploy) -

IV. INTEGRATING

+## IV. INTEGRATING {#integrating} -

Registering webhooks

+### Registering webhooks {#integrating-webhooks} Shipit handles several webhook types by default, listed in `Shipit::Wehbooks::DEFAULT_HANDLERS`, in order to implement default behaviours. Extra handler blocks can be registered via `Shipit::Webhooks.register_handler`. Valid handlers need only implement the `call` method - meaning any object which implements `call` - blocks, procs, or lambdas are valid. The webhooks controller will pass a `params` argument to the handler. Some examples: -

Registering a Plain old Ruby Object as a handler

+#### Registering a Plain old Ruby Object as a handler ```ruby class PullRequestHandler @@ -729,7 +761,7 @@ end Shipit::Webhooks.register_handler('pull_request', PullRequestHandler) ``` -

Registering a Block as a handler

+#### Registering a Block as a handler ```ruby Shipit::Webhooks.register_handler('pull_request') do |params| @@ -737,27 +769,27 @@ Shipit::Webhooks.register_handler('pull_request') do |params| end ``` -Multiple handler blocks can be registered. If any raise errors, execution will be halted and the request will be reported failed to github. +Multiple handler blocks can be registered. If any raise errors, execution will be halted and the request will be reported failed to GitHub. -

V. CONTRIBUTING

+## V. CONTRIBUTING {#contributing} -

Instructions

+### Instructions {#contributing-instructions} -1. Fork it ( https://github.com/shopify/shipit-engine/fork ) -1. Create your feature branch (git checkout -b my-new-feature) -1. Commit your changes (git commit -am 'Add some feature') -1. Push to the branch (git push origin my-new-feature) -1. Create a new Pull Request +1. [Fork it](https://github.com/shopify/shipit-engine/fork) +2. Create your feature branch (git checkout -b my-new-feature) +3. Commit your changes (git commit -am 'Add some feature') +4. Push to the branch (git push origin my-new-feature) +5. Create a new Pull Request -

Local development

+### Local development {#contributing-local-dev} -This repository has a [test/dummy](/test/dummy) app in it which can be used for local development without having to setup a new rails application. +This repository has a [test/dummy](/test/dummy) app in it which can be used for local development without having to set up a new Rails application. Run `./bin/bootstrap` in order to bootstrap the dummy application. The bootstrap script is going to: -- Copy `config/secrets.development.example.yml` to `config/secrets.development.yml`; -- Make sure all dependencies are installed; -- Create and seed database (recreate database if already available); +* Copy `config/secrets.development.example.yml` to `config/secrets.development.yml`; +* Make sure all dependencies are installed; +* Create and seed database (recreate database if already available); Run `./test/dummy/bin/rails server` to run the rails dummy application. From 563f4d84b00e01c185338a42d7c273b1e415ce65 Mon Sep 17 00:00:00 2001 From: Tayler Phillips Date: Wed, 24 Sep 2025 16:44:38 -0400 Subject: [PATCH 4/5] include changelog entry --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5e036dfc..9821d367c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Unreleased +* Update the bundler discovery section to let the `sed` command work with modern macs (issue #1371) +* Add config for `post` scripts on both deploy and rollback sections to run `on_error` +* Updated the code to allow for post scripts to run on command errors +* Added extra env config (FAILED/FAILURE_MESSAGE) for use in pre-/post-scripts +* Updated README to include `dependencies.bundler.frozen` +* Updated README to include sections on post scripts with on_error flag +* Refactored README to use mostly pure markdown instead of HTML + # 0.41.0 * Display banner with optional/default message when deployment checks are triggered (#1422) From e5c551be75049b645324bf1abe9792ba484b826f Mon Sep 17 00:00:00 2001 From: Tayler Phillips Date: Tue, 7 Oct 2025 16:30:21 -0400 Subject: [PATCH 5/5] Restore functionality to tasks on the stash show page Rendering the tasks with a specific partial was blowing up when tryiong to click links for the generic task partial. --- app/models/shipit/rollback.rb | 2 +- app/views/shipit/stacks/show.html.erb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/shipit/rollback.rb b/app/models/shipit/rollback.rb index dfa1f76e6..89d1e2b59 100644 --- a/app/models/shipit/rollback.rb +++ b/app/models/shipit/rollback.rb @@ -31,7 +31,7 @@ def commit_range end def to_partial_path - 'deploys/deploy' + 'shipit/deploys/deploy' end def report_complete! diff --git a/app/views/shipit/stacks/show.html.erb b/app/views/shipit/stacks/show.html.erb index 35035830b..e866260a7 100644 --- a/app/views/shipit/stacks/show.html.erb +++ b/app/views/shipit/stacks/show.html.erb @@ -36,7 +36,7 @@

Previous Deploys

    - <%= render partial: 'shipit/tasks/task', collection: @tasks %> + <%= render @tasks %>
<% end %>