engineyard-serverside-adapter 1.3.1.pre

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+