diff --git a/README.md b/README.md
index 872aee5b7..7ef1214bd 100644
--- a/README.md
+++ b/README.md
@@ -123,6 +123,7 @@ The settings in the `shipit.yml` file relate to the different things you can do
* [Custom Tasks](#custom-tasks) (`tasks`)
* [Custom links](#custom-links) (`links`)
* [Review Process](#review-process) (`review.checklist`, `review.monitoring`, `review.checks`)
+* [Inherit From](#inherit-from)(`inherit_from`)
All the settings in `shipit.yml` are optional. Most applications can be deployed from Shipit without any configuration.
@@ -130,6 +131,8 @@ Also, if your repository is deployed different ways depending on the environment
For example for a stack like: `my-org/my-repo/staging`, `shipit.staging.yml` will have priority over `shipit.yml`.
+In order to reduce duplication across different environment specific files, you can specify an `inherit_from` key in your relevant `shipit.yml` file. This key expects a string of the file name to inherit from. If this key is specified, a deep-merge will be performed on the file therein, overwriting any duplicated values from the parent. See [Inherit From](#inherit-from)(`inherit_From`) for example.
+
Lastly, if you override the `app_name` configuration in your Shipit deployment, `yourapp.yml` and `yourapp.staging.yml` will work.
* * *
@@ -613,6 +616,42 @@ review:
- bundle exec rake db:migrate:status
```
+
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:
+
+``` yaml
+# shipit.production.yml
+inherit_from: shipit.staging.yml
+
+machine:
+ environment:
+ PUBLIC: true
+```
+
+``` yaml
+# shipit.staging.yml
+inherit_from: shipit.yml
+
+deploy:
+ override:
+ - ./some_deployment_process.sh ${PUBLIC}
+```
+
+``` yaml
+# shipit.yml
+
+machine:
+ environment:
+ TEST: true
+ PUBLIC: false
+```
+
+Loading 'shipit.production.yml' would result in:
+```rb
+{"machine"=>{"environment"=>{"TEST"=>true, "PUBLIC"=>true}}, "deploy"=>{"override"=>["./some_deployment_process.sh ${PUBLIC}"]}}
+```
+
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.
diff --git a/app/models/shipit/deploy_spec/file_system.rb b/app/models/shipit/deploy_spec/file_system.rb
index e5efe2491..0fca76532 100644
--- a/app/models/shipit/deploy_spec/file_system.rb
+++ b/app/models/shipit/deploy_spec/file_system.rb
@@ -100,7 +100,8 @@ def load_config
return { 'deploy' => { 'pre' => [shipit_not_obeying_bare_file_echo_command, 'exit 1'] } }
end
- read_config(config_file_path)
+ config_obj = read_config(config_file_path)
+ build_config(config_file_path, config_obj)
end
def shipit_file_names_in_priority_order
@@ -136,6 +137,20 @@ def app_name
@app_name ||= Shipit.app_name.downcase
end
+ SHIPIT_CONFIG_INHERIT_FROM_KEY = "inherit_from"
+ def build_config(path, config_obj)
+ return config_obj if config_obj.blank? || !config_obj.key?(SHIPIT_CONFIG_INHERIT_FROM_KEY)
+
+ inherits_from_path = path.dirname.join(config_obj.delete(SHIPIT_CONFIG_INHERIT_FROM_KEY))
+ if inherits_from_path.exist?
+ inherits_config_obj = read_config(inherits_from_path)
+ config_obj = inherits_config_obj.deep_merge(config_obj)
+ path = inherits_from_path
+ end
+
+ build_config(path, config_obj)
+ end
+
def read_config(path)
SafeYAML.load(path.read) if path.exist?
end
diff --git a/test/models/shipit/deploy_spec/file_system_test.rb b/test/models/shipit/deploy_spec/file_system_test.rb
index ece77e67b..4e48c939a 100644
--- a/test/models/shipit/deploy_spec/file_system_test.rb
+++ b/test/models/shipit/deploy_spec/file_system_test.rb
@@ -63,6 +63,35 @@ class FileSystemTest < ActiveSupport::TestCase
assert loaded_config["deploy"]["pre"].include?('exit 1')
end
+ test '#load_config builds proper config if inherit_from is present' do
+ Shipit.expects(:respect_bare_shipit_file?).returns(true).at_least_once
+ stack = shipit_stacks(:shipit)
+ deploy_spec = Shipit::DeploySpec::FileSystem.new(Dir.tmpdir, stack)
+ deploy_spec.expects(:config_file_path).returns(Pathname.new(Dir.tmpdir) + '/shipit_1.yml').at_least_once
+ deploy_spec.expects(:read_config).returns(SafeYAML.load(deploy_spec_inherit_from_yaml), SafeYAML.load(deploy_spec_yaml)).at_least_once
+ Pathname.any_instance.stubs(:exist?).returns(true)
+ loaded_config = deploy_spec.send(:load_config)
+ assert loaded_config.key?("deploy")
+ assert loaded_config["deploy"].key?("pre")
+ assert loaded_config["deploy"]["pre"].include?("test 2")
+ assert loaded_config["deploy"]["override"].include?("test 11")
+ assert_not loaded_config.include?(Shipit::DeploySpec::FileSystem::SHIPIT_CONFIG_INHERIT_FROM_KEY)
+ end
+
+ test '#load_config builds valid config if inherit_from path is missing' do
+ Shipit.expects(:respect_bare_shipit_file?).returns(true).at_least_once
+ stack = shipit_stacks(:shipit)
+ deploy_spec = Shipit::DeploySpec::FileSystem.new(Dir.tmpdir, stack)
+ deploy_spec.expects(:config_file_path).returns(Pathname.new(Dir.tmpdir) + '/shipit_1.yml').at_least_once
+ deploy_spec.expects(:read_config).returns(SafeYAML.load(deploy_spec_inherit_from_yaml)).at_least_once
+ Pathname.any_instance.stubs(:exist?).returns(false)
+ loaded_config = deploy_spec.send(:load_config)
+ assert loaded_config.key?("deploy")
+ assert_not loaded_config["deploy"].include?("pre")
+ assert loaded_config["deploy"]["override"].include?("test 11")
+ assert_not loaded_config.include?(Shipit::DeploySpec::FileSystem::SHIPIT_CONFIG_INHERIT_FROM_KEY)
+ end
+
def deploy_spec_yaml
<<~EOYAML
deploy:
@@ -73,6 +102,15 @@ def deploy_spec_yaml
EOYAML
end
+ def deploy_spec_inherit_from_yaml
+ <<~EOYAML
+ inherit_from: shipit.yml
+ deploy:
+ override:
+ - test 11
+ EOYAML
+ end
+
def deploy_spec_missing_deploy_yaml
<<~EOYAML
production_platform: