├── lib
├── capistrano-secrets-yml.rb
└── capistrano
│ ├── secrets_yml
│ ├── version.rb
│ ├── paths.rb
│ └── helpers.rb
│ ├── secrets_yml.rb
│ └── tasks
│ └── secrets_yml.rake
├── .gitignore
├── Rakefile
├── Gemfile
├── CHANGELOG.md
├── LICENSE.md
├── capistrano-secrets-yml.gemspec
└── README.md
/lib/capistrano-secrets-yml.rb:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | Gemfile.lock
3 | *.gem
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Specify your gem's dependencies in *.gemspec
4 | gemspec
5 |
--------------------------------------------------------------------------------
/lib/capistrano/secrets_yml/version.rb:
--------------------------------------------------------------------------------
1 | module Capistrano
2 | module SecretsYml
3 | VERSION = '1.2.1'
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/lib/capistrano/secrets_yml.rb:
--------------------------------------------------------------------------------
1 | require "capistrano/secrets_yml/paths"
2 | require "capistrano/secrets_yml/helpers"
3 | load File.expand_path("../tasks/secrets_yml.rake", __FILE__)
4 |
--------------------------------------------------------------------------------
/lib/capistrano/secrets_yml/paths.rb:
--------------------------------------------------------------------------------
1 | require "pathname"
2 |
3 | module Capistrano
4 | module SecretsYml
5 | module Paths
6 |
7 | def secrets_yml_local_path
8 | Pathname.new fetch(:secrets_yml_local_path)
9 | end
10 |
11 | def secrets_yml_remote_path
12 | shared_path.join fetch(:secrets_yml_remote_path)
13 | end
14 |
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ### master
4 | - move `capistrano-secrets-yml.rb` file to the proper dir (not sure why it is
5 | required though)
6 |
7 | ### v1.0.0, 2014-10-07
8 | - added a check if `secrets.yml` is removed from git
9 | - add content to the README
10 | - improve checks for `secrets.yml`
11 |
12 | ### v0.0.1, 2014-10-07
13 | - started the project
14 | - first working version
15 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (C) 2014 Bruno Sutic
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the "Software"),
5 | to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
7 | and/or sell copies of the Software, and to permit persons to whom the
8 | Software is furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included
11 | in all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
14 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
15 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
16 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
18 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
19 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/capistrano-secrets-yml.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | lib = File.expand_path('../lib', __FILE__)
3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4 | require 'capistrano/secrets_yml/version'
5 |
6 | Gem::Specification.new do |gem|
7 | gem.name = 'capistrano-secrets-yml'
8 | gem.version = Capistrano::SecretsYml::VERSION
9 | gem.authors = ['Bruno Sutic']
10 | gem.email = ['bruno.sutic@gmail.com']
11 | gem.description = <<-EOF.gsub(/^\s+/, '')
12 | Capistrano tasks for automating `secrets.yml` file handling for Rails 4+ apps.
13 |
14 | This plugins syncs contents of your local secrets file and copies that to
15 | the remote server.
16 | EOF
17 | gem.summary = 'Capistrano tasks for automating `secrets.yml` file handling for Rails 4+ apps.'
18 | gem.homepage = 'https://github.com/capistrano-plugins/capistrano-secrets-yml'
19 |
20 | gem.files = `git ls-files`.split($/)
21 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
22 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
23 | gem.require_paths = ['lib']
24 |
25 | gem.license = 'MIT'
26 |
27 | gem.add_dependency 'capistrano', '>= 3.10'
28 | gem.add_dependency 'sshkit', '>= 1.17.0'
29 | gem.add_development_dependency 'rake', '~> 12.3'
30 | end
31 |
--------------------------------------------------------------------------------
/lib/capistrano/secrets_yml/helpers.rb:
--------------------------------------------------------------------------------
1 | require "yaml"
2 |
3 | module Capistrano
4 | module SecretsYml
5 | module Helpers
6 |
7 | def local_secrets_yml(env)
8 | @local_secrets_yml ||= YAML.load_file(secrets_yml_local_path)
9 | @local_secrets_yml[env]
10 | end
11 |
12 | def secrets_yml_env
13 | fetch(:secrets_yml_env).to_s
14 | end
15 |
16 | def secrets_yml_content
17 | { secrets_yml_env => local_secrets_yml(secrets_yml_env) }.to_yaml
18 | end
19 |
20 | # error helpers
21 |
22 | def check_git_tracking_error
23 | puts
24 | puts "Error - please remove '#{fetch(:secrets_yml_local_path)}' from git:"
25 | puts
26 | puts " $ git rm --cached #{fetch(:secrets_yml_local_path)}"
27 | puts
28 | puts "and gitignore it:"
29 | puts
30 | puts " $ echo '#{fetch(:secrets_yml_local_path)}' >> .gitignore"
31 | puts
32 | end
33 |
34 | def check_config_present_error
35 | puts
36 | puts "Error - '#{secrets_yml_env}' config not present in '#{fetch(:secrets_yml_local_path)}'."
37 | puts "Please populate it."
38 | puts
39 | end
40 |
41 | def check_secrets_file_exists_error
42 | puts
43 | puts "Error - '#{fetch(:secrets_yml_local_path)}' file does not exists, and it's required."
44 | puts
45 | end
46 |
47 | end
48 | end
49 | end
50 |
51 |
--------------------------------------------------------------------------------
/lib/capistrano/tasks/secrets_yml.rake:
--------------------------------------------------------------------------------
1 | include Capistrano::SecretsYml::Paths
2 | include Capistrano::SecretsYml::Helpers
3 | namespace :load do task :defaults do
4 | set :secrets_yml_local_path, "config/secrets.yml"
5 | set :secrets_yml_remote_path, "config/secrets.yml"
6 | set :secrets_yml_env, -> { fetch(:rails_env) || fetch(:stage) }
7 | end
8 | end
9 |
10 | namespace :secrets_yml do
11 |
12 | task :check_secrets_file_exists do
13 | next if File.exist?(secrets_yml_local_path)
14 | check_secrets_file_exists_error
15 | exit 1
16 | end
17 |
18 | task :check_git_tracking do
19 | next unless system("git ls-files #{fetch(:secrets_yml_local_path)} --error-unmatch >/dev/null 2>&1")
20 | check_git_tracking_error
21 | exit 1
22 | end
23 |
24 | task :check_config_present do
25 | next unless local_secrets_yml(secrets_yml_env).nil?
26 | check_config_present_error
27 | exit 1
28 | end
29 |
30 | desc "secrets.yml file checks"
31 | task :check do
32 | raise(":deploy_to in your app/config/deploy/\#{environment}.rb file cannot contain ~") if shared_path.to_s.include?('~') # SCP doesn't support ~ in the path
33 | invoke "secrets_yml:check_secrets_file_exists"
34 | invoke "secrets_yml:check_git_tracking"
35 | invoke "secrets_yml:check_config_present"
36 | end
37 |
38 | desc "Setup `secrets.yml` file on the server(s)"
39 | task setup: [:check] do
40 | content = secrets_yml_content
41 | on release_roles :all do
42 | execute :mkdir, "-pv", File.dirname(secrets_yml_remote_path)
43 | Net::SCP.upload!(self.host.hostname, self.host.user, StringIO.new(content), secrets_yml_remote_path, ssh: { port: self.host.port })
44 | end
45 | end
46 |
47 | # Update `linked_files` after the deploy starts so that users'
48 | # `secrets_yml_remote_path` override is respected.
49 | task :secrets_yml_symlink do
50 | set :linked_files, fetch(:linked_files, []).push(fetch(:secrets_yml_remote_path))
51 | end
52 | after "deploy:started", "secrets_yml:secrets_yml_symlink"
53 |
54 | end
55 |
56 | desc "Server setup tasks"
57 | task :setup do
58 | invoke "secrets_yml:setup"
59 | end
60 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Capistrano::SecretsYml
2 |
3 | Capistrano tasks for handling `secrets.yml` when deploying Rails 4+ apps.
4 |
5 | ### Install
6 |
7 | Add this to `Gemfile`:
8 |
9 | group :development do
10 | gem 'capistrano', '~> 3.10.0'
11 | gem 'capistrano-secrets-yml', '~> 1.1.0'
12 | end
13 |
14 | And then:
15 |
16 | $ bundle install
17 |
18 | ### Setup and usage
19 |
20 | - Make sure your local `config/secrets.yml` is not git tracked. It **should be on
21 | the disk**, but gitignored.
22 |
23 | - Populate production secrets in local `config/secrets.yml`:
24 |
25 | production:
26 | secret_key_base: d6ced...
27 |
28 | - Add to `Capfile`:
29 |
30 | require 'capistrano/secrets_yml'
31 |
32 | - Create `secrets.yml` file on the remote server by executing this task:
33 |
34 | $ bundle exec cap production setup
35 |
36 | You can now proceed with other deployment tasks.
37 |
38 | #### What if a new config is added to secrets file?
39 |
40 | - add it to local `config/secrets.yml`:
41 |
42 | production:
43 | secret_key_base: d6ced...
44 | foobar: some_other_secret
45 |
46 | - if you're working in a team where other people have the deploy rights, compare
47 | you local `secrets.yml` with the one on the server. This is to ensure you
48 | didn't miss an update.
49 | - copy to the server:
50 |
51 | if you only need to update secrets.yml
52 |
53 | $ bundle exec cap production secrets_yml:setup
54 |
55 | or if other Capistrano plugins need to be setup
56 |
57 | $ bundle exec cap production setup
58 |
59 | - notify your colleagues that have the deploy rights that the remote
60 | `secrets.yml` has been updated so they can change their copy.
61 |
62 |
63 | ### How it works
64 |
65 | When you execute `$ bundle exec production setup`:
66 |
67 | - secrets from your local `secrets.yml` are copied to the servers in your config/deploy/{environment}.rb files using the user: value. a.
68 | - only "stage" secrets are copied: if you are deploying to `production`,
69 | only production secrets are copied there
70 | - on the server secrets file is located in `#{shared_path}/config/secrets.yml`
71 |
72 | On deployment:
73 |
74 | - secrets file is automatically symlinked to `#{current_path}/config/secrets.yml`
75 |
76 | ### Configuration
77 |
78 | None.
79 |
80 | ### More Capistrano automation?
81 |
82 | Check out [capistrano-plugins](https://github.com/capistrano-plugins) github org.
83 |
84 | ### FAQ
85 |
86 | - shouldn't we be keeping configuration in environment variables as per
87 | [12 factor app rules](http://12factor.net/config)?
88 |
89 | On Heroku, yes.
90 | With Capistrano, those env vars still have to be written somewhere on the disk
91 | and used with a tool like [dotenv](https://github.com/bkeepers/dotenv).
92 |
93 | Since we have to keep configuration on the disk anyway, it probably makes
94 | sense to use Rails 4 built-in `secrets.yml` mechanism.
95 |
96 | ### License
97 |
98 | [MIT](LICENSE.md)
99 |
--------------------------------------------------------------------------------