engineyard-serverside-adapter 1.3.1.pre

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.
@@ -0,0 +1,4 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
4
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :gemcutter
2
+
3
+ # Specify your gem's dependencies in engineyard-serverside-adapter.gemspec
4
+ gemspec
@@ -0,0 +1,9 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'spec/rake/spectask'
5
+ Spec::Rake::SpecTask.new(:spec) do |spec|
6
+ spec.libs << 'lib' << 'spec'
7
+ spec.spec_files = FileList['spec/**/*_spec.rb']
8
+ end
9
+ task :default => :spec
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path("../lib/engineyard-serverside-adapter/version", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "engineyard-serverside-adapter"
6
+ s.version = EY::Serverside::Adapter::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = []
9
+ s.email = []
10
+ s.homepage = "http://github.com/engineyard/engineyard-serverside-adapter"
11
+ s.summary = "Adapter for speaking to engineyard-serverside"
12
+ s.description = "A separate adapter for speaking the CLI language of the engineyard-serverside gem."
13
+
14
+ s.required_rubygems_version = ">= 1.3.6"
15
+ s.rubyforge_project = "engineyard-serverside-adapter"
16
+
17
+ s.add_dependency "escape", "~> 0.0.4"
18
+ s.add_dependency "json_pure", "~> 1.4.6"
19
+ s.add_development_dependency "bundler", ">= 1.0.0"
20
+ s.add_development_dependency "rspec", "~> 1.3.0"
21
+
22
+ s.files = `git ls-files`.split("\n")
23
+ s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
24
+ s.require_path = 'lib'
25
+ end
@@ -0,0 +1,53 @@
1
+ require 'pathname'
2
+
3
+ module EY
4
+ module Serverside
5
+ class Adapter
6
+ autoload :Action, 'engineyard-serverside-adapter/action'
7
+ autoload :Arguments, 'engineyard-serverside-adapter/arguments'
8
+ autoload :Command, 'engineyard-serverside-adapter/command'
9
+ autoload :Deploy, 'engineyard-serverside-adapter/deploy'
10
+ autoload :DisableMaintenancePage, 'engineyard-serverside-adapter/disable_maintenance_page'
11
+ autoload :EnableMaintenancePage, 'engineyard-serverside-adapter/enable_maintenance_page'
12
+ autoload :Integrate, 'engineyard-serverside-adapter/integrate'
13
+ autoload :Rollback, 'engineyard-serverside-adapter/rollback'
14
+ autoload :VERSION, 'engineyard-serverside-adapter/version'
15
+
16
+ ENGINEYARD_SERVERSIDE_VERSION = ENV['ENGINEYARD_SERVERSIDE_VERSION'] || VERSION
17
+
18
+ def initialize(gem_bin_path = "")
19
+ @gem_bin_path = Pathname.new(gem_bin_path)
20
+ @arguments = Arguments.new
21
+
22
+ yield @arguments if block_given?
23
+ end
24
+
25
+ def deploy(&b)
26
+ Deploy.new(new_action_args, &b)
27
+ end
28
+
29
+ def disable_maintenance_page(&b)
30
+ DisableMaintenancePage.new(new_action_args, &b)
31
+ end
32
+
33
+ def enable_maintenance_page(&b)
34
+ EnableMaintenancePage.new(new_action_args, &b)
35
+ end
36
+
37
+ def integrate(&b)
38
+ Integrate.new(new_action_args, &b)
39
+ end
40
+
41
+ def rollback(&b)
42
+ Rollback.new(new_action_args, &b)
43
+ end
44
+
45
+ private
46
+
47
+ def new_action_args
48
+ {:arguments => @arguments.dup, :gem_bin_path => @gem_bin_path}
49
+ end
50
+
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,94 @@
1
+ require 'escape'
2
+ require 'pathname'
3
+
4
+ module EY
5
+ module Serverside
6
+ class Adapter
7
+ class Action
8
+
9
+ def initialize(options = {}, &block)
10
+ @gem_bin_path = Pathname.new(options[:gem_bin_path] || "")
11
+ arguments = options[:arguments] || Arguments.new
12
+ block.call arguments if block
13
+
14
+ extract_state_from_arguments(arguments)
15
+ validate!
16
+ end
17
+
18
+ def call(&block)
19
+ block.call check_and_install_command.to_s
20
+ block.call action_command.to_s
21
+ end
22
+
23
+ def verbose
24
+ @state[:verbose]
25
+ end
26
+
27
+ class << self
28
+ attr_accessor :options
29
+
30
+ def option(name, type, extra={:required => false})
31
+ self.options ||= {}
32
+ options[name] = extra.merge({:type => type})
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def extract_state_from_arguments(arguments)
39
+ @state = self.class.options.inject({}) do |acc, (option_name, option_attrs)|
40
+ acc.merge(option_name => arguments.send(option_name))
41
+ end
42
+ end
43
+
44
+ def check_and_install_command
45
+ "(#{check_command}) || (#{install_command})"
46
+ end
47
+
48
+ def check_command
49
+ escaped_engineyard_serverside_version = ENGINEYARD_SERVERSIDE_VERSION.gsub(/\./, '\.')
50
+
51
+ [
52
+ Escape.shell_command([gem_path, "list", "engineyard-serverside"]),
53
+ Escape.shell_command(["grep", "engineyard-serverside "]),
54
+ Escape.shell_command(["egrep", "-q", "#{escaped_engineyard_serverside_version}[,)]"]),
55
+ ].join(" | ")
56
+ end
57
+
58
+ def install_command
59
+ # rubygems looks at *.gem in its current directory for
60
+ # installation candidates, so we have to make sure it
61
+ # runs from a directory with no gem files in it.
62
+ #
63
+ # rubygems help suggests that --remote will disable this
64
+ # behavior, but it doesn't.
65
+ install_command = "cd `mktemp -d` && #{gem_path} install engineyard-serverside --no-rdoc --no-ri -v #{ENGINEYARD_SERVERSIDE_VERSION}"
66
+ Escape.shell_command(['sudo', 'sh', '-c', install_command])
67
+ end
68
+
69
+ def gem_path
70
+ @gem_bin_path.join('gem').to_s
71
+ end
72
+
73
+ def action_command
74
+ cmd = Command.new(@gem_bin_path, *task)
75
+ @state.each do |option_name, value|
76
+ option_type = self.class.options[option_name][:type]
77
+ switch = "--" + option_name.to_s.gsub(/_/, '-')
78
+ cmd.send("#{option_type}_argument", switch, value)
79
+ end
80
+ cmd
81
+ end
82
+
83
+ def validate!
84
+ self.class.options.each do |option_name, option_attrs|
85
+ if option_attrs[:required] && !@state[option_name]
86
+ raise ArgumentError, "Required field '#{option_name}' not provided."
87
+ end
88
+ end
89
+ end
90
+
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,61 @@
1
+ module EY
2
+ module Serverside
3
+ class Adapter
4
+ class Arguments < Struct.new(:app, :config, :framework_env, :instances, :migrate, :ref, :repo, :stack, :verbose)
5
+
6
+ def app=(app)
7
+ enforce_nonempty!('app', app)
8
+ super
9
+ end
10
+
11
+ def framework_env=(framework_env)
12
+ enforce_nonempty!('framework_env', framework_env)
13
+ super
14
+ end
15
+
16
+ def instances=(instances)
17
+ unless instances.respond_to?(:each)
18
+ raise ArgumentError, "Value for 'instances' must look like an enumerable."
19
+ end
20
+
21
+ if instances.empty?
22
+ raise ArgumentError, "Value for 'instances' must not be empty."
23
+ end
24
+
25
+ instances.each do |instance|
26
+ unless instance.respond_to?(:[]) && instance[:hostname] && instance[:roles]
27
+ raise ArgumentError, "Malformed instance #{instance}; it must have both [:hostname] and [:roles]"
28
+ end
29
+ end
30
+
31
+ super
32
+ end
33
+
34
+ def ref=(ref)
35
+ enforce_nonempty!('ref', ref)
36
+ super
37
+ end
38
+
39
+ def repo=(repo)
40
+ enforce_nonempty!('repo', repo)
41
+ super
42
+ end
43
+
44
+ def stack=(stack)
45
+ enforce_nonempty!('stack', stack)
46
+ super
47
+ end
48
+
49
+ private
50
+
51
+ def enforce_nonempty!(name, value)
52
+ if value.to_s.empty?
53
+ raise ArgumentError, "Value for '#{name}' must be non-empty."
54
+ end
55
+ end
56
+
57
+ end
58
+ end
59
+ end
60
+ end
61
+
@@ -0,0 +1,66 @@
1
+ require 'escape'
2
+ require 'json'
3
+
4
+ module EY
5
+ module Serverside
6
+ class Adapter
7
+ class Command
8
+ def initialize(bin_path, *task)
9
+ @task = task
10
+ @arguments = []
11
+ @binary = bin_path.join('engineyard-serverside').to_s
12
+ end
13
+
14
+ def to_s
15
+ Escape.shell_command [@binary, "_#{ENGINEYARD_SERVERSIDE_VERSION}_"] + @task + @arguments.sort_by { |x| x.first }.flatten
16
+ end
17
+
18
+ def array_argument(switch, values)
19
+ compacted = values.compact.sort
20
+ if compacted.any?
21
+ @arguments << [switch, compacted]
22
+ end
23
+ end
24
+
25
+ def boolean_argument(switch, value)
26
+ if value
27
+ @arguments << [switch]
28
+ end
29
+ end
30
+
31
+ def hash_argument(switch, pairs)
32
+ if pairs.any? {|k,v| !v.nil?}
33
+ @arguments << [switch, pairs.reject { |k,v| v.nil? }.map { |pair| pair.join(':') }.sort]
34
+ end
35
+ end
36
+
37
+ def instances_argument(_, instances)
38
+ role_pairs = instances.inject({}) do |roles, instance|
39
+ roles.merge(instance[:hostname] => instance[:roles].join(','))
40
+ end
41
+ hash_argument('--instance-roles', role_pairs)
42
+
43
+ role_pairs = instances.inject({}) do |roles, instance|
44
+ roles.merge(instance[:hostname] => instance[:name])
45
+ end
46
+ hash_argument('--instance-names', role_pairs)
47
+
48
+ array_argument('--instances', instances.map{|i| i[:hostname]})
49
+ end
50
+
51
+ def json_argument(switch, value)
52
+ if value
53
+ string_argument(switch, value.to_json)
54
+ end
55
+ end
56
+
57
+ def string_argument(switch, value)
58
+ unless value.to_s.empty?
59
+ @arguments << [switch, value]
60
+ end
61
+ end
62
+
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,25 @@
1
+ module EY
2
+ module Serverside
3
+ class Adapter
4
+ class Deploy < Action
5
+
6
+ option :app, :string, :required => true
7
+ option :stack, :string, :required => true
8
+ option :instances, :instances, :required => true
9
+ option :config, :json
10
+ option :verbose, :boolean
11
+ option :framework_env, :string, :required => true
12
+ option :ref, :string, :required => true
13
+ option :repo, :string, :required => true
14
+ option :migrate, :string
15
+
16
+ private
17
+
18
+ def task
19
+ ['deploy']
20
+ end
21
+
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,19 @@
1
+ module EY
2
+ module Serverside
3
+ class Adapter
4
+ class DisableMaintenancePage < Action
5
+
6
+ option :app, :string, :required => true
7
+ option :instances, :instances, :required => true
8
+ option :verbose, :boolean
9
+
10
+ private
11
+
12
+ def task
13
+ ['deploy', 'disable_maintenance_page']
14
+ end
15
+
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module EY
2
+ module Serverside
3
+ class Adapter
4
+ class EnableMaintenancePage < Action
5
+
6
+ option :app, :string, :required => true
7
+ option :instances, :instances, :required => true
8
+ option :verbose, :boolean
9
+
10
+ private
11
+
12
+ def task
13
+ ['deploy', 'enable_maintenance_page']
14
+ end
15
+
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ module EY
2
+ module Serverside
3
+ class Adapter
4
+ class Integrate < Action
5
+
6
+ option :app, :string, :required => true
7
+ option :stack, :string, :required => true
8
+ option :instances, :instances, :required => true
9
+ option :framework_env, :string, :required => true
10
+ option :verbose, :boolean
11
+
12
+ private
13
+
14
+ def task
15
+ ['integrate']
16
+ end
17
+
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module EY
2
+ module Serverside
3
+ class Adapter
4
+ class Rollback < Action
5
+
6
+ option :app, :string, :required => true
7
+ option :stack, :string, :required => true
8
+ option :instances, :instances, :required => true
9
+ option :config, :json
10
+ option :verbose, :boolean
11
+
12
+ private
13
+
14
+ def task
15
+ ['deploy', 'rollback']
16
+ end
17
+
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,7 @@
1
+ module EY
2
+ module Serverside
3
+ class Adapter
4
+ VERSION = "1.3.1.pre"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,125 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples_for "a serverside action" do
4
+ before(:each) do
5
+ @adapter = described_class.new do |args|
6
+ args.app = 'app-from-adapter-new'
7
+ args.instances = [{:hostname => 'localhost', :roles => %w[a b c]}]
8
+ args.framework_env = 'production'
9
+ args.ref = 'master'
10
+ args.repo = 'git@github.com:engineyard/engineyard-serverside.git'
11
+ args.stack = 'nginx_unicorn'
12
+ args
13
+ end
14
+ end
15
+
16
+ it "gives you an Arguments already set up from when you instantiated the adapter" do
17
+ command = @adapter.send(@method) do |args|
18
+ args.app.should == 'app-from-adapter-new'
19
+ end
20
+ end
21
+
22
+ it "applies both sets of changes" do
23
+ action = @adapter.send(@method) do |args|
24
+ args.verbose = true
25
+ end
26
+
27
+ command = last_command(action)
28
+ command.should include('--app app-from-adapter-new')
29
+ command.should include('--verbose')
30
+ end
31
+
32
+ it "does not let arguments changes propagate back up to the adapter" do
33
+ command1 = @adapter.send(@method) do |args|
34
+ args.app = 'sporkr'
35
+ end
36
+
37
+ @adapter.send(@method) do |args|
38
+ args.app.should == 'app-from-adapter-new'
39
+ end
40
+ end
41
+
42
+ context "with no pathname specified" do
43
+ it "begins both commands with no path" do
44
+ action = @adapter.send(@method) do |args|
45
+ args.verbose = true
46
+ end
47
+
48
+ commands = all_commands(action)
49
+ commands.first.should =~ /^\(gem/
50
+ commands.last.should =~ /^engineyard-serverside/
51
+ end
52
+ end
53
+
54
+ context "with a pathname specified" do
55
+ it "begins both commands with the given path" do
56
+ adapter = described_class.new("/usr/local/grin") do |args|
57
+ args.app = 'app-from-adapter-new'
58
+ args.instances = [{:hostname => 'localhost', :roles => %w[a b c]}]
59
+ args.framework_env = 'production'
60
+ args.ref = 'master'
61
+ args.repo = 'git@github.com:engineyard/engineyard-serverside.git'
62
+ args.stack = 'nginx_unicorn'
63
+ args
64
+ end
65
+
66
+ action = adapter.send(@method) do |args|
67
+ args.verbose = true
68
+ end
69
+
70
+ commands = all_commands(action)
71
+ commands.first.should =~ %r{^\(/usr/local/grin/gem}
72
+ commands.last.should =~ %r{^/usr/local/grin/engineyard-serverside}
73
+ end
74
+ end
75
+ end
76
+
77
+ describe EY::Serverside::Adapter do
78
+ context ".new" do
79
+ it "lets you access the arguments" do
80
+ adapter = described_class.new do |args|
81
+ args.app = 'myapp'
82
+ end
83
+ end
84
+
85
+ it "does not require a block" do
86
+ lambda { described_class.new }.should_not raise_error
87
+ end
88
+ end
89
+
90
+ [
91
+ :deploy,
92
+ :disable_maintenance_page,
93
+ :enable_maintenance_page,
94
+ :integrate,
95
+ :rollback,
96
+ ].each do |method|
97
+ context "##{method}" do
98
+ before { @method = method }
99
+ it_should_behave_like "a serverside action"
100
+ end
101
+ end
102
+
103
+ context "mapping of methods to action classes" do
104
+ before(:each) do
105
+ @adapter = described_class.new do |args|
106
+ args.app = 'app-from-adapter-new'
107
+ args.instances = [{:hostname => 'localhost', :roles => %w[a b c]}]
108
+ args.framework_env = 'production'
109
+ args.ref = 'master'
110
+ args.repo = 'git@github.com:engineyard/engineyard-serverside.git'
111
+ args.stack = 'nginx_unicorn'
112
+ args
113
+ end
114
+ end
115
+
116
+ it "gives you the right command" do
117
+ @adapter.enable_maintenance_page.should be_kind_of(EY::Serverside::Adapter::EnableMaintenancePage)
118
+ @adapter.disable_maintenance_page.should be_kind_of(EY::Serverside::Adapter::DisableMaintenancePage)
119
+ @adapter.deploy.should be_kind_of(EY::Serverside::Adapter::Deploy)
120
+ @adapter.integrate.should be_kind_of(EY::Serverside::Adapter::Integrate)
121
+ @adapter.rollback.should be_kind_of(EY::Serverside::Adapter::Rollback)
122
+ end
123
+ end
124
+
125
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe EY::Serverside::Adapter::Arguments do
4
+ def raises_argument_error(&block)
5
+ lambda {
6
+ block.call(described_class.new)
7
+ }.should raise_error(ArgumentError)
8
+ end
9
+
10
+ it "raises an ArgumentError immediately when instances is empty" do
11
+ raises_argument_error do |arguments|
12
+ arguments.instances = []
13
+ end
14
+ end
15
+
16
+ it "raises an ArgumentError immediately when instances is something totally silly" do
17
+ raises_argument_error do |arguments|
18
+ arguments.instances = 42
19
+ end
20
+ end
21
+
22
+ it "raises an ArgumentError immediately when instances contains something totally silly" do
23
+ raises_argument_error do |arguments|
24
+ arguments.instances = [nil]
25
+ end
26
+ end
27
+
28
+ end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+
3
+ describe EY::Serverside::Adapter::Deploy do
4
+ it_should_behave_like "it installs engineyard-serverside"
5
+
6
+ it_should_behave_like "it accepts app"
7
+ it_should_behave_like "it accepts framework_env"
8
+ it_should_behave_like "it accepts instances"
9
+ it_should_behave_like "it accepts migrate"
10
+ it_should_behave_like "it accepts ref"
11
+ it_should_behave_like "it accepts repo"
12
+ it_should_behave_like "it accepts stack"
13
+ it_should_behave_like "it accepts verbose"
14
+
15
+ it_should_require :app
16
+ it_should_require :instances
17
+ it_should_require :framework_env
18
+ it_should_require :ref
19
+ it_should_require :repo
20
+ it_should_require :stack
21
+
22
+ it_should_behave_like "it treats config as optional"
23
+ it_should_behave_like "it treats migrate as optional"
24
+
25
+ context "with valid arguments" do
26
+ let(:command) do
27
+ adapter = described_class.new do |arguments|
28
+ arguments.app = "rackapp"
29
+ arguments.framework_env = 'production'
30
+ arguments.config = {'a' => 1}
31
+ arguments.instances = [{:hostname => 'localhost', :roles => %w[han solo], :name => 'chewie'}]
32
+ arguments.migrate = 'rake db:migrate'
33
+ arguments.ref = 'master'
34
+ arguments.repo = 'git@github.com:engineyard/engineyard-serverside.git'
35
+ arguments.stack = "nginx_unicorn"
36
+ end
37
+ last_command(adapter)
38
+ end
39
+
40
+ it "puts the config in the command line as json" do
41
+ command.should =~ /--config '#{Regexp.quote '{"a":1}'}'/
42
+ end
43
+
44
+ it "invokes exactly the right command" do
45
+ command.should == "engineyard-serverside _#{EY::Serverside::Adapter::VERSION}_ deploy --app rackapp --config '{\"a\":1}' --framework-env production --instance-names localhost:chewie --instance-roles localhost:han,solo --instances localhost --migrate 'rake db:migrate' --ref master --repo git@github.com:engineyard/engineyard-serverside.git --stack nginx_unicorn"
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe EY::Serverside::Adapter::DisableMaintenancePage do
4
+ it_should_behave_like "it installs engineyard-serverside"
5
+
6
+ it_should_behave_like "it accepts app"
7
+ it_should_behave_like "it accepts instances"
8
+ it_should_behave_like "it accepts verbose"
9
+
10
+ it_should_require :app
11
+ it_should_require :instances
12
+
13
+ context "with valid arguments" do
14
+ let(:command) do
15
+ adapter = described_class.new do |arguments|
16
+ arguments.app = "rackapp"
17
+ arguments.instances = [{:hostname => 'localhost', :roles => %w[han solo], :name => 'chewie'}]
18
+ end
19
+ last_command(adapter)
20
+ end
21
+
22
+ it "invokes exactly the right command" do
23
+ command.should == "engineyard-serverside _#{EY::Serverside::Adapter::VERSION}_ deploy disable_maintenance_page --app rackapp --instance-names localhost:chewie --instance-roles localhost:han,solo --instances localhost"
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe EY::Serverside::Adapter::EnableMaintenancePage do
4
+ it_should_behave_like "it installs engineyard-serverside"
5
+
6
+ it_should_behave_like "it accepts app"
7
+ it_should_behave_like "it accepts instances"
8
+ it_should_behave_like "it accepts verbose"
9
+
10
+ it_should_require :app
11
+ it_should_require :instances
12
+
13
+ context "with valid arguments" do
14
+
15
+ let(:command) do
16
+ adapter = described_class.new do |arguments|
17
+ arguments.app = "rackapp"
18
+ arguments.instances = [{:hostname => 'localhost', :roles => %w[han solo], :name => 'chewie'}]
19
+ end
20
+ last_command(adapter)
21
+ end
22
+
23
+ it "invokes exactly the right command" do
24
+ command.should == "engineyard-serverside _#{EY::Serverside::Adapter::VERSION}_ deploy enable_maintenance_page --app rackapp --instance-names localhost:chewie --instance-roles localhost:han,solo --instances localhost"
25
+ end
26
+ end
27
+
28
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe EY::Serverside::Adapter::Integrate do
4
+ it_should_behave_like "it installs engineyard-serverside"
5
+
6
+ it_should_behave_like "it accepts app"
7
+ it_should_behave_like "it accepts framework_env"
8
+ it_should_behave_like "it accepts instances"
9
+ it_should_behave_like "it accepts stack"
10
+ it_should_behave_like "it accepts verbose"
11
+
12
+ it_should_require :app
13
+ it_should_require :stack
14
+ it_should_require :instances
15
+ it_should_require :framework_env
16
+
17
+ context "with valid arguments" do
18
+ let(:command) do
19
+ adapter = described_class.new do |arguments|
20
+ arguments.app = "rackapp"
21
+ arguments.instances = [{:hostname => 'localhost', :roles => %w[han solo], :name => 'chewie'}]
22
+ arguments.stack = "nginx_unicorn"
23
+ arguments.framework_env = "production"
24
+ end
25
+ last_command(adapter)
26
+ end
27
+
28
+ it "invokes exactly the right command" do
29
+ command.should == "engineyard-serverside _#{EY::Serverside::Adapter::VERSION}_ integrate --app rackapp --framework-env production --instance-names localhost:chewie --instance-roles localhost:han,solo --instances localhost --stack nginx_unicorn"
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe EY::Serverside::Adapter::Rollback do
4
+ it_should_behave_like "it installs engineyard-serverside"
5
+
6
+ it_should_behave_like "it accepts app"
7
+ it_should_behave_like "it accepts instances"
8
+ it_should_behave_like "it accepts stack"
9
+ it_should_behave_like "it accepts verbose"
10
+
11
+ it_should_require :app
12
+ it_should_require :stack
13
+ it_should_require :instances
14
+
15
+ context "with valid arguments" do
16
+ let(:command) do
17
+ adapter = described_class.new do |arguments|
18
+ arguments.app = "rackapp"
19
+ arguments.instances = [{:hostname => 'localhost', :roles => %w[han solo], :name => 'chewie'}]
20
+ arguments.stack = "nginx_unicorn"
21
+ arguments.config = {'a' => 1}
22
+ end
23
+ last_command(adapter)
24
+ end
25
+
26
+ it "puts the config in the command line as json" do
27
+ command.should =~ /--config '#{Regexp.quote '{"a":1}'}'/
28
+ end
29
+
30
+ it "invokes exactly the right command" do
31
+ command.should == "engineyard-serverside _#{EY::Serverside::Adapter::VERSION}_ deploy rollback --app rackapp --config '{\"a\":1}' --instance-names localhost:chewie --instance-roles localhost:han,solo --instances localhost --stack nginx_unicorn"
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format specdoc
3
+ --backtrace
@@ -0,0 +1,182 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'engineyard-serverside-adapter'
5
+ require 'pp'
6
+
7
+ module ArgumentsHelpers
8
+ def valid_options
9
+ {
10
+ :app => 'rackapp',
11
+ :framework_env => 'production',
12
+ :instances => [{:hostname => 'localhost', :roles => %w[han solo], :name => 'chewie'}],
13
+ :ref => 'master',
14
+ :repo => 'git@github.com:engineyard/engineyard-serverside.git',
15
+ :stack => 'nginx_unicorn',
16
+ }
17
+ end
18
+
19
+ def valid_arguments() arguments_without() end # without nothing --> valid :)
20
+
21
+ def arguments_with(fields)
22
+ arguments = valid_arguments
23
+ fields.each do |field, value|
24
+ arguments.send("#{field}=", value)
25
+ end
26
+ arguments
27
+ end
28
+
29
+ def arguments_without(*fields)
30
+ arguments = EY::Serverside::Adapter::Arguments.new
31
+ valid_options.each do |field, value|
32
+ arguments.send("#{field}=", value) unless fields.include?(field)
33
+ end
34
+ arguments
35
+ end
36
+
37
+ def all_commands(adapter)
38
+ commands = []
39
+ adapter.call { |command| commands << command }
40
+ commands
41
+ end
42
+
43
+ def last_command(adapter)
44
+ all_commands(adapter).last
45
+ end
46
+ end
47
+
48
+ module RequiredFieldHelpers
49
+ def it_should_require(field)
50
+ context "field #{field}" do
51
+ it "is just fine when #{field} is there" do
52
+ lambda { described_class.new(:arguments => valid_arguments) }.should_not raise_error
53
+ end
54
+
55
+ it "raises an error if #{field} is missing" do
56
+ lambda { described_class.new(:arguments => arguments_without(field)) }.should raise_error(ArgumentError)
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ Spec::Runner.configure do |config|
63
+ config.include ArgumentsHelpers
64
+ config.extend RequiredFieldHelpers
65
+
66
+ shared_examples_for "it installs engineyard-serverside" do
67
+ it "checks for and installs engineyard-serverside before invoking it" do
68
+ adapter = described_class.new(:arguments => valid_arguments)
69
+
70
+ all_commands(adapter).size.should == 2
71
+ installation_command = all_commands(adapter).first
72
+
73
+ # of course, the only way to be sure is to actually run it, but
74
+ # this gives us regression-proofing
75
+ version = EY::Serverside::Adapter::ENGINEYARD_SERVERSIDE_VERSION
76
+ escaped_version = version.gsub(/\./, '\\.')
77
+ installation_command.should == "(gem list engineyard-serverside | grep 'engineyard-serverside ' | egrep -q '#{escaped_version}[,)]') || (sudo sh -c 'cd `mktemp -d` && gem install engineyard-serverside --no-rdoc --no-ri -v #{version}')"
78
+
79
+
80
+ installation_command.should =~ /gem list engineyard-serverside/
81
+ installation_command.should =~ /egrep -q /
82
+ installation_command.should =~ /gem install engineyard-serverside.*-v #{Regexp.quote EY::Serverside::Adapter::VERSION}/
83
+ end
84
+ end
85
+
86
+ shared_examples_for "it accepts verbose" do
87
+ context "the --verbose arg" do
88
+ it "is present when you set verbose to true" do
89
+ adapter = described_class.new(:arguments => arguments_with(:verbose => true))
90
+ last_command(adapter).should =~ /--verbose/
91
+ end
92
+
93
+ it "is absent when you set verbose to false" do
94
+ adapter = described_class.new(:arguments => arguments_with(:verbose => false))
95
+ last_command(adapter).should_not =~ /--verbose/
96
+ end
97
+
98
+ it "is absent when you omit verbose" do
99
+ adapter = described_class.new(:arguments => valid_arguments)
100
+ last_command(adapter).should_not =~ /--verbose/
101
+ end
102
+ end
103
+ end
104
+
105
+ {
106
+ :app => '--app',
107
+ :stack => '--stack',
108
+ :framework_env => '--framework-env',
109
+ :ref => '--ref',
110
+ :repo => '--repo',
111
+ :migrate => '--migrate',
112
+ }.each do |arg, switch|
113
+ shared_examples_for "it accepts #{arg}" do
114
+ it "puts the #{switch} arg in the command line" do
115
+ adapter = described_class.new(:arguments => arguments_with(arg => 'word'))
116
+ last_command(adapter).should =~ /#{switch} word/
117
+ end
118
+
119
+ it "handles arguments that need to be escaped" do
120
+ adapter = described_class.new(:arguments => arguments_with(arg => 'two words'))
121
+ last_command(adapter).should =~ /#{switch} 'two words'/
122
+ end
123
+ end
124
+
125
+ shared_examples_for "it treats #{arg} as optional" do
126
+ it "omits #{switch} when you don't give it #{arg}" do
127
+ adapter = described_class.new(:arguments => arguments_without(arg))
128
+ last_command(adapter).should_not include(switch)
129
+ end
130
+ end
131
+
132
+ end
133
+
134
+ shared_examples_for "it treats config as optional" do
135
+ it "omits --config when you don't give it config" do
136
+ adapter = described_class.new(:arguments => arguments_without(:config))
137
+ last_command(adapter).should_not include('--config')
138
+ end
139
+ end
140
+
141
+ shared_examples_for "it accepts instances" do
142
+ context "given an unnamed instance" do
143
+ it "puts the instance in the command line" do
144
+ adapter = described_class.new(:arguments => arguments_with(
145
+ :instances => [{:hostname => 'localhost', :roles => %w[han solo], :name => nil}]
146
+ ))
147
+ command = last_command(adapter)
148
+ command.should =~ /--instances localhost/
149
+ command.should =~ /--instance-roles localhost:han,solo/
150
+ command.should_not =~ /--instance-names/
151
+ end
152
+ end
153
+
154
+ context "given a named instance" do
155
+ it "puts the instance in the command line" do
156
+ adapter = described_class.new(:arguments => arguments_with(
157
+ :instances => [{:hostname => 'localhost', :roles => %w[han solo], :name => 'chewie'}]
158
+ ))
159
+ command = last_command(adapter)
160
+ command.should =~ /--instances localhost/
161
+ command.should =~ /--instance-roles localhost:han,solo/
162
+ command.should =~ /--instance-names localhost:chewie/
163
+ end
164
+ end
165
+
166
+ context "given multiple instances" do
167
+ it "puts the instance in the command line" do
168
+ adapter = described_class.new(:arguments => arguments_with({
169
+ :instances => [
170
+ {:hostname => 'localhost', :roles => %w[wookie], :name => 'chewie'},
171
+ {:hostname => 'crazy-ass-amazon-1243324321.domain', :roles => %w[bounty-hunter], :name => nil},
172
+ {:hostname => 'simpler.domain', :roles => %w[pilot scruffy-lookin-nerf-herder], :name => 'han'},
173
+ ]
174
+ }))
175
+ command = last_command(adapter)
176
+ command.should =~ /--instances crazy-ass-amazon-1243324321.domain localhost simpler.domain/
177
+ command.should =~ /--instance-roles crazy-ass-amazon-1243324321.domain:bounty-hunter localhost:wookie simpler.domain:pilot,scruffy-lookin-nerf-herder/
178
+ command.should =~ /--instance-names localhost:chewie simpler.domain:han/
179
+ end
180
+ end
181
+ end
182
+ end
metadata ADDED
@@ -0,0 +1,156 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: engineyard-serverside-adapter
3
+ version: !ruby/object:Gem::Version
4
+ hash: 961915976
5
+ prerelease: true
6
+ segments:
7
+ - 1
8
+ - 3
9
+ - 1
10
+ - pre
11
+ version: 1.3.1.pre
12
+ platform: ruby
13
+ authors: []
14
+
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2010-09-15 00:00:00 -07:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: escape
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ~>
29
+ - !ruby/object:Gem::Version
30
+ hash: 23
31
+ segments:
32
+ - 0
33
+ - 0
34
+ - 4
35
+ version: 0.0.4
36
+ type: :runtime
37
+ version_requirements: *id001
38
+ - !ruby/object:Gem::Dependency
39
+ name: json_pure
40
+ prerelease: false
41
+ requirement: &id002 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ hash: 11
47
+ segments:
48
+ - 1
49
+ - 4
50
+ - 6
51
+ version: 1.4.6
52
+ type: :runtime
53
+ version_requirements: *id002
54
+ - !ruby/object:Gem::Dependency
55
+ name: bundler
56
+ prerelease: false
57
+ requirement: &id003 !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ hash: 23
63
+ segments:
64
+ - 1
65
+ - 0
66
+ - 0
67
+ version: 1.0.0
68
+ type: :development
69
+ version_requirements: *id003
70
+ - !ruby/object:Gem::Dependency
71
+ name: rspec
72
+ prerelease: false
73
+ requirement: &id004 !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ~>
77
+ - !ruby/object:Gem::Version
78
+ hash: 27
79
+ segments:
80
+ - 1
81
+ - 3
82
+ - 0
83
+ version: 1.3.0
84
+ type: :development
85
+ version_requirements: *id004
86
+ description: A separate adapter for speaking the CLI language of the engineyard-serverside gem.
87
+ email: []
88
+
89
+ executables: []
90
+
91
+ extensions: []
92
+
93
+ extra_rdoc_files: []
94
+
95
+ files:
96
+ - .gitignore
97
+ - Gemfile
98
+ - Rakefile
99
+ - engineyard-serverside-adapter.gemspec
100
+ - lib/engineyard-serverside-adapter.rb
101
+ - lib/engineyard-serverside-adapter/action.rb
102
+ - lib/engineyard-serverside-adapter/arguments.rb
103
+ - lib/engineyard-serverside-adapter/command.rb
104
+ - lib/engineyard-serverside-adapter/deploy.rb
105
+ - lib/engineyard-serverside-adapter/disable_maintenance_page.rb
106
+ - lib/engineyard-serverside-adapter/enable_maintenance_page.rb
107
+ - lib/engineyard-serverside-adapter/integrate.rb
108
+ - lib/engineyard-serverside-adapter/rollback.rb
109
+ - lib/engineyard-serverside-adapter/version.rb
110
+ - spec/adapter_spec.rb
111
+ - spec/arguments_spec.rb
112
+ - spec/deploy_spec.rb
113
+ - spec/disable_maintenance_page_spec.rb
114
+ - spec/enable_maintenance_page_spec.rb
115
+ - spec/integrate_spec.rb
116
+ - spec/rollback_spec.rb
117
+ - spec/spec.opts
118
+ - spec/spec_helper.rb
119
+ has_rdoc: true
120
+ homepage: http://github.com/engineyard/engineyard-serverside-adapter
121
+ licenses: []
122
+
123
+ post_install_message:
124
+ rdoc_options: []
125
+
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ hash: 3
134
+ segments:
135
+ - 0
136
+ version: "0"
137
+ required_rubygems_version: !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ hash: 23
143
+ segments:
144
+ - 1
145
+ - 3
146
+ - 6
147
+ version: 1.3.6
148
+ requirements: []
149
+
150
+ rubyforge_project: engineyard-serverside-adapter
151
+ rubygems_version: 1.3.7
152
+ signing_key:
153
+ specification_version: 3
154
+ summary: Adapter for speaking to engineyard-serverside
155
+ test_files: []
156
+