ey_secrets 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ M2JjNjMyZGE5NjUxZDYwNGU1YTkzNmIxYmZjMjE1ZjMzMWY1N2YyNg==
5
+ data.tar.gz: !binary |-
6
+ ZWI0YWIyNjc5OTg0NzViMDVhMGViZDk0MTM0N2NhNTFiNTg2YTY3ZA==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ ZGFlMjgxOGY1ZjQwNWQwNGU2NTQ1MDY2NzIyOWNiOTI3YzJhY2YyOGMxZmEx
10
+ Njc0MTEyYzg2NGQ1NzQ4ZGY5OWM0NzBmMjgyMThjMjZmOTg5OWJlNmJlNjQ1
11
+ N2ZjYmE2ODlkNWU2ZjFlOTNkMWZmNDRmNjdiZDUyNzBkMDY3ZTA=
12
+ data.tar.gz: !binary |-
13
+ Yzg1NTRlODc0MTJmYjdjYmVlOTg1ZmJiYjYyMDg1ZmMyMjliMmJjMmM5MTQx
14
+ MDU0NGUwYmU5YjQzMTE5ZTRlMWQ1YjU3NzE2MDE3NDJiNjQ5MmNlNzYyMzJm
15
+ ZjMxMmQxMWZmYzkyZDRmMDVmYzk1ZDkyYzYyZTU2NmQ5ZTNhOWQ=
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ spec/tmp
16
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ey_config.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Tim Fischbach
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # Engine Yard Secrets
2
+
3
+ Manage sensible configuration files accross Engine Yard instances.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'ey_secrets'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install ey_secrets
18
+
19
+ ## Usage
20
+
21
+ The repository is expected to contain one folder per Engine Yard
22
+ environment, each containing files with `.env` extension. For example:
23
+
24
+ staging/
25
+ secrets.env
26
+ production/
27
+ secrets.env
28
+
29
+ After commiting and pushing changes inside the config repository, run:
30
+
31
+ $ cd my_project
32
+ $ eysecrets update -e production
33
+
34
+ Now all files of the form `production/*.env` are copied
35
+ to `/data/<app_name>/shared/config` on all instances of the production
36
+ environment. The command also restarts passenger and the monit group
37
+ `<app_name>_resque`.
38
+
39
+ ## Contributing
40
+
41
+ 1. Fork it
42
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
43
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
44
+ 4. Push to the branch (`git push origin my-new-feature`)
45
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/eysecrets ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.expand_path('../../lib', __FILE__))
3
+ require 'ey_secrets'
4
+
5
+ begin
6
+ EySecrets::Cli.start
7
+ rescue StandardError => e
8
+ puts e.message
9
+ exit(1)
10
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ey_secrets/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'ey_secrets'
8
+ spec.version = EySecrets::VERSION
9
+ spec.authors = ['Tim Fischbach']
10
+ spec.email = ['tfischbach@codevise.de']
11
+ spec.summary = 'Manage sensible configuration files accross Engine Yard instances.'
12
+ spec.homepage = 'http://github.com/tf/ey_secrets'
13
+ spec.license = 'MIT'
14
+
15
+ spec.files = `git ls-files`.split($/)
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.add_runtime_dependency 'engineyard-cloud-client', '~> 1.0'
21
+ spec.add_runtime_dependency 'thor'
22
+ spec.add_runtime_dependency 'rainbow', '~> 2.0'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.4'
25
+ spec.add_development_dependency 'rake'
26
+ spec.add_development_dependency 'rspec', '~> 3.0'
27
+ end
@@ -0,0 +1,26 @@
1
+ module EySecrets
2
+ class CopyFiles < Struct.new(:instance, :repository, :options)
3
+ include CommandBuilder
4
+
5
+ def build
6
+ ensure_config_dir!
7
+ copy_env_files
8
+ end
9
+
10
+ private
11
+
12
+ def ensure_config_dir!
13
+ ssh("if [ ! -d #{instance.shared_config_dir} ]; then mkdir -p #{instance.shared_config_dir}; fi")
14
+ end
15
+
16
+ def copy_env_files
17
+ env_files.each do |file|
18
+ scp(file, File.join(instance.shared_config_dir, File.basename(file)))
19
+ end
20
+ end
21
+
22
+ def env_files
23
+ repository.glob("#{instance.environment.name}/*.env")
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,20 @@
1
+ module EySecrets
2
+ class Restart < Struct.new(:instance, :options)
3
+ include CommandBuilder
4
+
5
+ def build
6
+ restart_monit
7
+ restart_passenger
8
+ end
9
+
10
+ private
11
+
12
+ def restart_monit
13
+ ssh("sudo monit restart -g #{instance.app_name}_resque")
14
+ end
15
+
16
+ def restart_passenger
17
+ ssh("if [ -d #{File.join(instance.current_app_dir, 'tmp')} ]; then touch #{File.join(instance.current_app_dir, 'tmp', 'restart.txt')}; fi")
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,14 @@
1
+ module EySecrets
2
+ class Update < Struct.new(:application, :repository, :options)
3
+ include CompositeCommandBuilder
4
+
5
+ def build
6
+ repository.assert_ready_for_update
7
+
8
+ InstanceResolver.find!(application, options.merge(remotes: repository.remotes)).each do |instance|
9
+ action CopyFiles.new(instance, repository, options)
10
+ action Restart.new(instance, options)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,25 @@
1
+ require 'thor'
2
+ require 'rainbow'
3
+
4
+ module EySecrets
5
+ class Cli < Thor
6
+ method_option 'environment', aliases: '-e', type: :string, required: true, banner: 'Environment to update.'
7
+ method_option 'app', aliases: '-a', type: :string, banner: 'App to update.'
8
+ method_option 'dry_run', aliases: '-n', type: :boolean, banner: 'Only print commands do not execute.'
9
+ desc 'update', 'Copy config files from deploy/config to instances.'
10
+ def update
11
+ Shell.execute!(Update.new(EngineYard.applications, Git.repository('.'), options), options) do |command|
12
+ puts(Rainbow(command).green)
13
+ end
14
+ rescue Error => e
15
+ puts(Rainbow(e.message).red)
16
+ exit 1
17
+ end
18
+
19
+ desc 'version', 'Display version information'
20
+ def version
21
+ puts Rainbow("EySecrets version #{VERSION}").green
22
+ end
23
+ map '-v' => :version
24
+ end
25
+ end
@@ -0,0 +1,50 @@
1
+ require 'engineyard-cloud-client'
2
+ require 'yaml'
3
+
4
+ module EySecrets
5
+ class EngineYard
6
+ def self.applications
7
+ new.applications
8
+ end
9
+
10
+ def applications
11
+ api.apps.map do |ey_app|
12
+ application_from(ey_app)
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def application_from(ey_app)
19
+ Application.new(ey_app.name, ey_app.repository_uri, environments_from(ey_app))
20
+ end
21
+
22
+ def environments_from(ey_app)
23
+ ey_app.app_environments.map do |ey_app_environment|
24
+ environment_from(ey_app, ey_app_environment.environment)
25
+ end
26
+ end
27
+
28
+ def environment_from(ey_app, ey_environment)
29
+ Environment.new(ey_environment.name, instances_from(ey_app, ey_environment))
30
+ end
31
+
32
+ def instances_from(ey_app, ey_environment)
33
+ ey_environment.instances.map do |ey_instance|
34
+ Instance.new(ey_instance.hostname, ey_environment.username, ey_app.name)
35
+ end
36
+ end
37
+
38
+ def api
39
+ EY::CloudClient.new(token: api_token)
40
+ end
41
+
42
+ def api_token
43
+ begin
44
+ YAML.load_file(File.join(ENV['HOME'], '.eyrc'))['api_token']
45
+ rescue
46
+ raise 'API Token not found. Please configure the engine yard cli.'
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,46 @@
1
+ module EySecrets
2
+ class Git < Struct.new(:path)
3
+ def self.repository(path)
4
+ new(path).repository
5
+ end
6
+
7
+ def repository
8
+ Repository.new(remotes, files, in_sync?)
9
+ end
10
+
11
+ private
12
+
13
+ def remotes
14
+ Dir.chdir(path) do
15
+ @remotes ||= `git remote -v`.scan(/\t[^\s]+\s/).map { |c| c.strip }.uniq
16
+ end
17
+ end
18
+
19
+ def files
20
+ Dir[File.join(path, '**', '*')].map do |file|
21
+ file.gsub(%r'^\./', '')
22
+ end
23
+ end
24
+
25
+ def in_sync?
26
+ in_sync_with_origin? && clean?
27
+ end
28
+
29
+ def in_sync_with_origin?
30
+ git_revision_diff_count == 0
31
+ end
32
+
33
+ def clean?
34
+ Dir.chdir(path) do
35
+ `git status --porcelain` == ''
36
+ end
37
+ end
38
+
39
+ def git_revision_diff_count
40
+ Dir.chdir(path) do
41
+ `git fetch && git rev-list HEAD..origin/master --count`.to_i +
42
+ `git rev-list origin/master..HEAD --count`.to_i
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,20 @@
1
+ require 'rainbow'
2
+
3
+ module EySecrets
4
+ class Shell
5
+ class CommandFailed < Error
6
+ end
7
+
8
+ def self.execute!(action, options = {})
9
+ action.commands.each do |command|
10
+ yield(command) if block_given?
11
+
12
+ unless options[:dry_run]
13
+ unless system(command)
14
+ raise(CommandFailed, "Error while executing #{command}")
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,23 @@
1
+ module EySecrets
2
+ module CommandBuilder
3
+ def scp(source, destination)
4
+ @commands << "scp #{source} #{instance.user_name}@#{instance.host_name}:#{destination}"
5
+ end
6
+
7
+ def ssh(command)
8
+ @commands << "ssh #{instance.user_name}@#{instance.host_name} \"#{command}\""
9
+ end
10
+
11
+ def commands
12
+ @commands = []
13
+ build
14
+ @commands
15
+ end
16
+
17
+ private
18
+
19
+ def build
20
+ raise(NotImplementedError)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,13 @@
1
+ module EySecrets
2
+ module CompositeCommandBuilder
3
+ def action(action)
4
+ @actions << action
5
+ end
6
+
7
+ def commands
8
+ @actions = []
9
+ build
10
+ @actions.map(&:commands).flatten
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,4 @@
1
+ module EySecrets
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,37 @@
1
+ module EySecrets
2
+ class InstanceResolver < Struct.new(:applications, :options)
3
+ def self.find!(applications, options)
4
+ new(applications, options).find!
5
+ end
6
+
7
+ def find!
8
+ find_environment!.instances
9
+ end
10
+
11
+ private
12
+
13
+ def find_environment!
14
+ find_app!.environments.find do |environment|
15
+ environment.name == options[:environment]
16
+ end || raise("Could not find environment '#{options[:environment]}' for app '#{options[:app]}'.")
17
+ end
18
+
19
+ def find_app!
20
+ find_app_by_name(options[:app]) ||
21
+ find_app_by_remotes(options[:remotes]) ||
22
+ raise("Could not find app with name '#{options[:name]}'.")
23
+ end
24
+
25
+ def find_app_by_name(name)
26
+ applications.find do |application|
27
+ application.name == name
28
+ end
29
+ end
30
+
31
+ def find_app_by_remotes(remotes)
32
+ applications.find do |application|
33
+ remotes.include?(application.config_repository_uri)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,7 @@
1
+ module EySecrets
2
+ class Application < Struct.new(:name, :repository_uri, :environments)
3
+ def config_repository_uri
4
+ repository_uri.gsub(/\.git$/, '_config.git')
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ module EySecrets
2
+ class Environment < Struct.new(:name, :instances)
3
+ def initialize(*)
4
+ super
5
+ instances.each do |instance|
6
+ instance.environment = self
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,15 @@
1
+ require 'rainbow'
2
+
3
+ module EySecrets
4
+ class Instance < Struct.new(:host_name, :user_name, :app_name)
5
+ attr_accessor :environment
6
+
7
+ def shared_config_dir
8
+ "/data/#{app_name}/shared/config"
9
+ end
10
+
11
+ def current_app_dir
12
+ "/data/#{app_name}/current/"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,20 @@
1
+ module EySecrets
2
+ class Repository < Struct.new(:remotes, :files, :ready_for_update)
3
+ class NotReadyForUpdate < Error
4
+ end
5
+
6
+ def ready_for_update?
7
+ ready_for_update
8
+ end
9
+
10
+ def glob(pattern)
11
+ files.select { |file| File.fnmatch(pattern, file) }
12
+ end
13
+
14
+ def assert_ready_for_update
15
+ unless ready_for_update?
16
+ raise(NotReadyForUpdate, "Please, commit and push your changes before updating config.")
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ module EySecrets
2
+ VERSION = "0.0.1"
3
+ end
data/lib/ey_secrets.rb ADDED
@@ -0,0 +1,23 @@
1
+ require 'ey_secrets/command_builder'
2
+ require 'ey_secrets/composite_command_builder'
3
+ require 'ey_secrets/error'
4
+ require 'ey_secrets/instance_resolver'
5
+
6
+ require 'ey_secrets/models/application'
7
+ require 'ey_secrets/models/environment'
8
+ require 'ey_secrets/models/instance'
9
+ require 'ey_secrets/models/repository'
10
+
11
+ require 'ey_secrets/actions/update'
12
+ require 'ey_secrets/actions/copy_files'
13
+ require 'ey_secrets/actions/restart'
14
+
15
+ require 'ey_secrets/adapters/cli'
16
+ require 'ey_secrets/adapters/engine_yard'
17
+ require 'ey_secrets/adapters/git'
18
+ require 'ey_secrets/adapters/shell'
19
+
20
+ require 'ey_secrets/version'
21
+
22
+ module EySecrets
23
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ module EySecrets
4
+ describe 'update' do
5
+ def applications
6
+ [
7
+ Application.new('my_app', 'git@git.exmaple.com:my_app.git', [
8
+ Environment.new('production', [
9
+ Instance.new('host', 'user', 'my_app')
10
+ ])
11
+ ])
12
+ ]
13
+ end
14
+
15
+ def repository(options = {})
16
+ Repository.new(['git@git.exmaple.com:my_app_config.git'],
17
+ ['production/secrets.env', 'staging/secrets.env'],
18
+ options.fetch(:ready_for_update, true))
19
+ end
20
+
21
+ it 'fails if repo is not ready for update' do
22
+ options = {app: 'my_app', environment: 'production'}
23
+
24
+ expect {
25
+ Update.new(applications, repository(ready_for_update: false), options).commands
26
+ }.to raise_error(Repository::NotReadyForUpdate)
27
+ end
28
+
29
+ it 'copies .env files from environment directory to instances' do
30
+ options = {app: 'my_app', environment: 'production'}
31
+
32
+ commands = Update.new(applications, repository, options).commands
33
+
34
+ expect(commands).to include('scp production/secrets.env user@host:/data/my_app/shared/config/secrets.env')
35
+ end
36
+
37
+ it 'restarts monit' do
38
+ options = {app: 'my_app', environment: 'production'}
39
+
40
+ commands = Update.new(applications, repository, options).commands
41
+
42
+ expect(commands).to include('ssh user@host "sudo monit restart -g my_app_resque"')
43
+ end
44
+
45
+ it 'restarts passenger' do
46
+ options = {app: 'my_app', environment: 'production'}
47
+
48
+ commands = Update.new(applications, repository, options).commands
49
+
50
+ expect(commands).to include(a_string_matching('touch /data/my_app/current/tmp/restart.txt'))
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,133 @@
1
+ require 'spec_helper'
2
+ require 'fileutils'
3
+
4
+ module EySecrets
5
+ describe Git do
6
+ describe '.repository' do
7
+ include FileUtils
8
+
9
+ describe '.remotes' do
10
+ it 'returns repository uris' do
11
+ in_sandbox do
12
+ create_bare_repo('origin_repo', ['file'])
13
+ `git clone origin_repo repo`
14
+
15
+ remotes = Git.repository('repo').remotes
16
+
17
+ expect(remotes).to eq([File.expand_path('origin_repo')])
18
+ end
19
+ end
20
+ end
21
+
22
+ describe '.files' do
23
+ it 'returns paths relative to CWD' do
24
+ in_sandbox do
25
+ create_bare_repo('origin_repo', ['file', 'some/file'])
26
+ `git clone origin_repo repo`
27
+
28
+ files = Git.repository('repo').files
29
+
30
+ expect(files.sort).to eq(['repo/file', 'repo/some', 'repo/some/file'])
31
+ end
32
+ end
33
+
34
+ it 'removes leading dot' do
35
+ in_sandbox do
36
+ create_bare_repo('origin_repo', ['file'])
37
+ `git clone origin_repo repo`
38
+
39
+ files = Dir.chdir('repo') do
40
+ Git.repository('.').files
41
+ end
42
+
43
+ expect(files).to eq(['file'])
44
+ end
45
+ end
46
+ end
47
+
48
+ describe '.ready_for_update?' do
49
+ it 'is true after clone' do
50
+ in_sandbox do
51
+ create_bare_repo('origin_repo', ['file'])
52
+ `git clone origin_repo repo`
53
+
54
+ repository = Git.repository('repo')
55
+
56
+ expect(repository).to be_ready_for_update
57
+ end
58
+ end
59
+
60
+ it 'is false if repo has untracked files' do
61
+ in_sandbox do
62
+ create_bare_repo('origin_repo', ['file'])
63
+ `git clone origin_repo repo`
64
+ `touch repo/other`
65
+
66
+ repository = Git.repository('repo')
67
+
68
+ expect(repository).not_to be_ready_for_update
69
+ end
70
+ end
71
+
72
+ it 'is false if repo has modified files' do
73
+ in_sandbox do
74
+ create_bare_repo('origin_repo', ['file'])
75
+ `git clone origin_repo repo`
76
+ `echo "change" > repo/file`
77
+
78
+ repository = Git.repository('repo')
79
+
80
+ expect(repository).not_to be_ready_for_update
81
+ end
82
+ end
83
+
84
+ it 'is false if repo has unpushed commits' do
85
+ in_sandbox do
86
+ create_bare_repo('origin_repo', ['file'])
87
+ `git clone origin_repo repo`
88
+ `(cd repo; echo "change" > file; git add .; git commit -m "change")`
89
+
90
+ repository = Git.repository('repo')
91
+
92
+ expect(repository).not_to be_ready_for_update
93
+ end
94
+ end
95
+
96
+ it 'is true if all commits have been pushed' do
97
+ in_sandbox do
98
+ create_bare_repo('origin_repo', ['file'])
99
+ `git clone origin_repo repo`
100
+ `(cd repo; echo "change" > file; git add .; git commit -m "change"; git push origin master 2> /dev/null)`
101
+
102
+ repository = Git.repository('repo')
103
+
104
+ expect(repository).to be_ready_for_update
105
+ end
106
+ end
107
+ end
108
+
109
+ def create_bare_repo(name, files = {})
110
+ mkdir('original_repo')
111
+ Dir.chdir('original_repo') do
112
+ `git init`
113
+ files.each do |file|
114
+ mkdir_p(File.dirname(file))
115
+ touch(file)
116
+ end
117
+ `git add .; git commit -m "initial commit"`
118
+ end
119
+ `git clone --bare original_repo #{name}`
120
+ end
121
+
122
+ def in_sandbox(&block)
123
+ rm_rf(sandbox_path)
124
+ mkdir_p(sandbox_path)
125
+ Dir.chdir(sandbox_path, &block)
126
+ end
127
+
128
+ def sandbox_path
129
+ File.join(PROJECT_ROOT, 'spec', 'tmp')
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ module EySecrets
4
+ describe Shell do
5
+ describe '.execute!' do
6
+ it 'runs commands of action' do
7
+ action = Class.new do
8
+ def commands
9
+ ['echo "output"']
10
+ end
11
+ end.new
12
+
13
+ expect {
14
+ quietly do
15
+ Shell.execute!(action)
16
+ end
17
+ }.not_to raise_error
18
+ end
19
+
20
+ it 'raises CommandFailed error if exit status is not 0' do
21
+ action = Class.new do
22
+ def commands
23
+ ['false']
24
+ end
25
+ end.new
26
+
27
+ expect {
28
+ quietly do
29
+ Shell.execute!(action)
30
+ end
31
+ }.to raise_error(Shell::CommandFailed)
32
+ end
33
+
34
+ def quietly(&block)
35
+ silence_stream(STDOUT, &block)
36
+ end
37
+
38
+ def silence_stream(stream)
39
+ old_stream = stream.dup
40
+ stream.reopen('/dev/null')
41
+ stream.sync = true
42
+ yield
43
+ ensure
44
+ stream.reopen(old_stream)
45
+ old_stream.close
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,8 @@
1
+ require 'rspec'
2
+
3
+ PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..')).freeze
4
+ $LOAD_PATH << File.join(PROJECT_ROOT, 'lib')
5
+
6
+ require 'ey_secrets'
7
+
8
+ Dir[File.join(PROJECT_ROOT, 'spec', 'support', '**', '*.rb')].each { |file| require(file) }
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ module EySecrets
4
+ describe Application do
5
+ describe '#config_repository_uri' do
6
+ it 'inserts config suffix into repository_uri' do
7
+ application = Application.new('my_app', 'git@git.example.com:my_app.git')
8
+
9
+ expect(application.config_repository_uri).to eq('git@git.example.com:my_app_config.git')
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ module EySecrets
4
+ describe InstanceResolver do
5
+ describe '.find!' do
6
+ it 'finds instances from specified environment' do
7
+ production_instance = Instance.new('production.host', 'user', 'my_app')
8
+ staging_instance = Instance.new('staging.host', 'user', 'my_app')
9
+ applications = [
10
+ Application.new('my_app', 'git@git.example.com:my_app.git', [
11
+ Environment.new('staging', [staging_instance]),
12
+ Environment.new('production', [production_instance])
13
+ ])
14
+ ]
15
+ options = {app: 'my_app', environment: 'production'}
16
+
17
+ instances = InstanceResolver.find!(applications, options)
18
+
19
+ expect(instances).to eq([production_instance])
20
+ end
21
+
22
+ it 'finds instances from specified application' do
23
+ app_instance = Instance.new('app.host', 'user', 'my_app')
24
+ other_app_instance = Instance.new('other.host', 'user', 'my_app')
25
+ applications = [
26
+ Application.new('my_app', 'git@git.example.com:my_app.git', [
27
+ Environment.new('production', [app_instance])
28
+ ]),
29
+ Application.new('other_app', 'git@git.example.com:other.git', [
30
+ Environment.new('production', [other_app_instance])
31
+ ])
32
+ ]
33
+ options = {app: 'my_app', environment: 'production'}
34
+
35
+ instances = InstanceResolver.find!(applications, options)
36
+
37
+ expect(instances).to eq([app_instance])
38
+ end
39
+
40
+ it 'finds instances from application with specified remotes' do
41
+ app_instance = Instance.new('app.host', 'user', 'my_app')
42
+ other_app_instance = Instance.new('other.host', 'user', 'my_app')
43
+ applications = [
44
+ Application.new('my_app', 'git@git.example.com:my_app.git', [
45
+ Environment.new('production', [app_instance])
46
+ ]),
47
+ Application.new('other_app', 'git@git.example.com:other.git', [
48
+ Environment.new('production', [other_app_instance])
49
+ ])
50
+ ]
51
+ options = {remotes: ['git@git.example.com:my_app_config.git'], environment: 'production'}
52
+
53
+ instances = InstanceResolver.find!(applications, options)
54
+
55
+ expect(instances).to eq([app_instance])
56
+ end
57
+ end
58
+ end
59
+ end
metadata ADDED
@@ -0,0 +1,165 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ey_secrets
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Tim Fischbach
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: engineyard-cloud-client
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: thor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rainbow
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.4'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '1.4'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ description:
98
+ email:
99
+ - tfischbach@codevise.de
100
+ executables:
101
+ - eysecrets
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - .gitignore
106
+ - Gemfile
107
+ - LICENSE.txt
108
+ - README.md
109
+ - Rakefile
110
+ - bin/eysecrets
111
+ - ey_secrets.gemspec
112
+ - lib/ey_secrets.rb
113
+ - lib/ey_secrets/actions/copy_files.rb
114
+ - lib/ey_secrets/actions/restart.rb
115
+ - lib/ey_secrets/actions/update.rb
116
+ - lib/ey_secrets/adapters/cli.rb
117
+ - lib/ey_secrets/adapters/engine_yard.rb
118
+ - lib/ey_secrets/adapters/git.rb
119
+ - lib/ey_secrets/adapters/shell.rb
120
+ - lib/ey_secrets/command_builder.rb
121
+ - lib/ey_secrets/composite_command_builder.rb
122
+ - lib/ey_secrets/error.rb
123
+ - lib/ey_secrets/instance_resolver.rb
124
+ - lib/ey_secrets/models/application.rb
125
+ - lib/ey_secrets/models/environment.rb
126
+ - lib/ey_secrets/models/instance.rb
127
+ - lib/ey_secrets/models/repository.rb
128
+ - lib/ey_secrets/version.rb
129
+ - spec/features/update_spec.rb
130
+ - spec/integration/ey_secrets/git_spec.rb
131
+ - spec/integration/ey_secrets/shell_spec.rb
132
+ - spec/spec_helper.rb
133
+ - spec/unit/ey_secrets/application_spec.rb
134
+ - spec/unit/ey_secrets/instance_resolver_spec.rb
135
+ homepage: http://github.com/tf/ey_secrets
136
+ licenses:
137
+ - MIT
138
+ metadata: {}
139
+ post_install_message:
140
+ rdoc_options: []
141
+ require_paths:
142
+ - lib
143
+ required_ruby_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ! '>='
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ required_rubygems_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ! '>='
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ requirements: []
154
+ rubyforge_project:
155
+ rubygems_version: 2.2.2
156
+ signing_key:
157
+ specification_version: 4
158
+ summary: Manage sensible configuration files accross Engine Yard instances.
159
+ test_files:
160
+ - spec/features/update_spec.rb
161
+ - spec/integration/ey_secrets/git_spec.rb
162
+ - spec/integration/ey_secrets/shell_spec.rb
163
+ - spec/spec_helper.rb
164
+ - spec/unit/ey_secrets/application_spec.rb
165
+ - spec/unit/ey_secrets/instance_resolver_spec.rb