engineyard 0.3.2 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,13 +1,14 @@
1
1
  module EY
2
2
  require 'engineyard/ruby_ext'
3
3
 
4
- VERSION = "0.3.2"
4
+ VERSION = "0.3.3"
5
5
 
6
- autoload :API, 'engineyard/api'
7
- autoload :Config, 'engineyard/config'
8
- autoload :Error, 'engineyard/error'
9
- autoload :Model, 'engineyard/model'
10
- autoload :Repo, 'engineyard/repo'
6
+ autoload :API, 'engineyard/api'
7
+ autoload :Collection, 'engineyard/collection'
8
+ autoload :Config, 'engineyard/config'
9
+ autoload :Error, 'engineyard/error'
10
+ autoload :Model, 'engineyard/model'
11
+ autoload :Repo, 'engineyard/repo'
11
12
 
12
13
  class UI
13
14
  # stub debug outside of the CLI
@@ -30,10 +30,6 @@ module EY
30
30
  @apps ||= EY::Model::App.from_array(request('/apps')["apps"], :api => self)
31
31
  end
32
32
 
33
- def environment_named(name, envs = self.environments)
34
- envs.find{|e| e.name == name }
35
- end
36
-
37
33
  def app_for_repo(repo)
38
34
  apps.find{|a| repo.urls.include?(a.repository_uri) }
39
35
  end
@@ -72,14 +68,17 @@ module EY
72
68
  rescue OpenSSL::SSL::SSLError
73
69
  raise RequestFailed, "SSL is misconfigured on your cloud"
74
70
  end
75
- raise RequestFailed, "Response body was empty" if resp.body.empty?
76
71
 
77
- begin
78
- data = JSON.parse(resp.body)
79
- EY.ui.debug("Response", data)
80
- rescue JSON::ParserError
81
- EY.ui.debug("Raw response", resp.body)
82
- raise RequestFailed, "Response was not valid JSON."
72
+ if resp.code == 204
73
+ data = nil
74
+ else
75
+ begin
76
+ data = JSON.parse(resp.body)
77
+ EY.ui.debug("Response", data)
78
+ rescue JSON::ParserError
79
+ EY.ui.debug("Raw response", resp.body)
80
+ raise RequestFailed, "Response was not valid JSON."
81
+ end
83
82
  end
84
83
 
85
84
  data
@@ -1,12 +1,12 @@
1
- require 'thor'
2
1
  require 'engineyard'
3
2
  require 'engineyard/error'
4
- require 'engineyard/cli/thor_fixes'
3
+ require 'engineyard/thor'
5
4
 
6
5
  module EY
7
- class CLI < Thor
8
- autoload :API, 'engineyard/cli/api'
9
- autoload :UI, 'engineyard/cli/ui'
6
+ class CLI < EY::Thor
7
+ autoload :API, 'engineyard/cli/api'
8
+ autoload :UI, 'engineyard/cli/ui'
9
+ autoload :Recipes, 'engineyard/cli/recipes'
10
10
 
11
11
  include Thor::Actions
12
12
 
@@ -28,54 +28,36 @@ module EY
28
28
  EY::CLI::Action::Deploy.call(env_name, branch, options)
29
29
  end
30
30
 
31
-
32
31
  desc "environments [--all]", "List cloud environments for this app, or all environments"
33
32
  method_option :all, :type => :boolean, :aliases => %(-a)
34
33
  def environments
35
- app, envs = app_and_envs(options[:all])
36
- if app
37
- EY.ui.say %|Cloud environments for #{app.name}:|
38
- EY.ui.print_envs(envs, EY.config.default_environment)
39
- elsif envs
40
- EY.ui.say %|Cloud environments:|
41
- EY.ui.print_envs(envs, EY.config.default_environment)
42
- else
43
- EY.ui.say %|You do not have any cloud environments.|
44
- end
34
+ apps = get_apps(options[:all])
35
+ EY.ui.warn(NoAppError.new(repo).message) unless apps.any? || options[:all]
36
+ EY.ui.print_envs(apps, EY.config.default_environment)
45
37
  end
46
38
  map "envs" => :environments
47
39
 
48
40
  desc "rebuild [ENV]", "Rebuild environment (ensure configuration is up-to-date)"
49
41
  def rebuild(name = nil)
50
- env = if name
51
- env = api.environment_named(name) or raise NoEnvironmentError.new(name)
52
- end
53
-
54
- unless env
55
- repo = Repo.new
56
- app = api.app_for_repo(repo) or raise NoAppError.new(repo)
57
- env = app.one_and_only_environment or raise EnvironmentError, "Unable to determine a single environment for the current application (found #{app.environments.size} environments)"
58
- end
59
-
42
+ env = fetch_environment(name)
60
43
  EY.ui.debug("Rebuilding #{env.name}")
61
44
  env.rebuild
62
45
  end
63
46
 
64
- desc "ssh ENV", "Open an ssh session to the environment's application server"
65
- def ssh(name)
66
- env = api.environment_named(name)
67
- if env && env.app_master
68
- Kernel.exec "ssh", "#{env.username}@#{env.app_master.public_hostname}", *ARGV[2..-1]
69
- elsif env
70
- raise NoAppMaster.new(env.name)
47
+ desc "ssh [ENV]", "Open an ssh session to the environment's application server"
48
+ def ssh(name = nil)
49
+ env = fetch_environment(name)
50
+
51
+ if env.app_master
52
+ Kernel.exec "ssh", "#{env.username}@#{env.app_master.public_hostname}"
71
53
  else
72
- EY.ui.warn %|Could not find an environment named "#{name}"|
54
+ raise NoAppMaster.new(env.name)
73
55
  end
74
56
  end
75
57
 
76
- desc "logs ENV", "Retrieve the latest logs for an enviornment"
77
- def logs(name)
78
- env_named(name).logs.each do |log|
58
+ desc "logs [ENV]", "Retrieve the latest logs for an environment"
59
+ def logs(name = nil)
60
+ fetch_environment(name).logs.each do |log|
79
61
  EY.ui.info log.instance_name
80
62
 
81
63
  if log.main
@@ -90,14 +72,8 @@ module EY
90
72
  end
91
73
  end
92
74
 
93
- desc "upload_recipes ENV", "Upload custom chef recipes from the current directory to ENV"
94
- def upload_recipes(name)
95
- if env_named(name).upload_recipes
96
- EY.ui.say "Recipes uploaded successfully"
97
- else
98
- EY.ui.error "Recipes upload failed"
99
- end
100
- end
75
+ desc "recipes COMMAND [ARGS]", "Commands related to custom recipes"
76
+ subcommand "recipes", EY::CLI::Recipes
101
77
 
102
78
  desc "version", "Print the version of the engineyard gem"
103
79
  def version
@@ -105,42 +81,5 @@ module EY
105
81
  end
106
82
  map ["-v", "--version"] => :version
107
83
 
108
- private
109
- def api
110
- @api ||= EY::CLI::API.new
111
- end
112
-
113
- def repo
114
- @repo ||= EY::Repo.new
115
- end
116
-
117
- def env_named(env_name)
118
- env = api.environment_named(env_name)
119
-
120
- if env.nil?
121
- raise EnvironmentError, "Environment '#{env_name}' can't be found\n" +
122
- "You can create it at #{EY.config.endpoint}"
123
- end
124
-
125
- env
126
- end
127
-
128
- def app_and_envs(all_envs = false)
129
- app = api.app_for_repo(repo)
130
-
131
- if all_envs || !app
132
- envs = api.environments
133
- EY.ui.warn(NoAppError.new(repo).message) unless app || all_envs
134
- [nil, envs]
135
- else
136
- envs = app.environments
137
- if envs.empty?
138
- EY.ui.warn %|You have no environments set up for the application "#{app.name}"|
139
- EY.ui.warn %|You can make one at #{EY.config.endpoint}|
140
- end
141
- envs.empty? ? [app, nil] : [app, envs]
142
- end
143
- end
144
-
145
84
  end # CLI
146
85
  end # EY
@@ -8,14 +8,10 @@ module EY
8
8
  def self.call(env_name, branch, options)
9
9
  env_name ||= EY.config.default_environment
10
10
 
11
- app = fetch_app
12
- env = fetch_environment(env_name, app)
11
+ app = fetch_app
12
+ env = fetch_environment(env_name, app)
13
13
  branch = fetch_branch(env.name, branch, options[:force])
14
-
15
- running = env.app_master && env.app_master.status == "running"
16
- raise EnvironmentError, "No running instances for environment #{env.name}\nStart one at #{EY.config.endpoint}" unless running
17
-
18
- master = env.app_master
14
+ master = fetch_app_master(env)
19
15
 
20
16
  EY.ui.info "Connecting to the server..."
21
17
  ensure_eysd_present(master, options[:install_eysd])
@@ -32,6 +28,18 @@ module EY
32
28
 
33
29
  private
34
30
 
31
+ def self.fetch_app_master(env)
32
+ master = env.app_master
33
+
34
+ if !master
35
+ raise EnvironmentError, "No running instances for environment #{env.name}\nStart one at #{EY.config.endpoint}"
36
+ elsif master.status != "running"
37
+ raise EnvironmentError, "Cannot deploy: application master's status is not \"running\" (green); it is \"#{master.status}\"."
38
+ end
39
+
40
+ master
41
+ end
42
+
35
43
  def self.api
36
44
  @api ||= EY::CLI::API.new
37
45
  end
@@ -52,13 +60,13 @@ module EY
52
60
  raise DeployArgumentError if !env_name && app.environments.size != 1
53
61
 
54
62
  env = if env_name
55
- api.environment_named(env_name, app.environments)
63
+ app.environments.match_one(env_name)
56
64
  else
57
65
  app.environments.first
58
66
  end
59
67
 
60
68
  # the environment exists, but doesn't have this app
61
- if !env && api.environment_named(env_name)
69
+ if !env && api.environments.named(env_name)
62
70
  raise EnvironmentError, "Environment '#{env_name}' doesn't run this application\nYou can add it at #{EY.config.endpoint}"
63
71
  end
64
72
 
@@ -23,8 +23,8 @@ module EY
23
23
  @token = self.class.fetch_token
24
24
  end
25
25
 
26
- def environment_named(env_name, envs = self.environments)
27
- super || find_environment_by_unambiguous_substring(env_name, envs)
26
+ def fetch_app_for_repo(repo)
27
+ app_for_repo(repo) || raise(NoAppError.new(repo))
28
28
  end
29
29
 
30
30
  def self.fetch_token
@@ -41,15 +41,6 @@ module EY
41
41
  end
42
42
  end
43
43
 
44
- private
45
- def find_environment_by_unambiguous_substring(env_name, envs)
46
- candidates = envs.find_all{|e| e.name[env_name] }
47
- if candidates.size > 1
48
- raise AmbiguousEnvironmentName.new(env_name, candidates.map {|e| e.name})
49
- end
50
- candidates.first
51
- end
52
-
53
44
  end
54
45
  end
55
46
  end
@@ -0,0 +1,14 @@
1
+ module EY
2
+ class CLI
3
+ class Recipes < EY::Thor
4
+ desc "recipes upload [ENV]", "Upload custom chef recipes from the current directory to ENV"
5
+ def upload(name = nil)
6
+ if fetch_environment(name).upload_recipes
7
+ EY.ui.say "Recipes uploaded successfully"
8
+ else
9
+ EY.ui.error "Recipes upload failed"
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -5,7 +5,7 @@
5
5
  # The fix is
6
6
  # http://github.com/smerritt/thor/commit/421b2e97684e67ee393a13b3873e1a784bb83f68
7
7
 
8
- class Thor
8
+ class ::Thor
9
9
  class Arguments
10
10
  private
11
11
 
@@ -59,17 +59,26 @@ module EY
59
59
  end
60
60
  end
61
61
 
62
- def print_envs(envs, default_env = nil)
63
- printable_envs = envs.map do |e|
64
- icount = e.instances_count
65
- iname = (icount == 1) ? "instance" : "instances"
62
+ def print_envs(apps, default_env_name = nil)
63
+ apps.each do |app|
64
+ puts app.name
65
+ if app.environments.any?
66
+ app.environments.each do |env|
67
+ short_name = env.shorten_name_for(app)
66
68
 
67
- e.name << " (default)" if e.name == default_env
68
- env = [e.name]
69
- env << "#{icount} #{iname}"
70
- env << e.apps.map{|a| a.name }.join(", ")
69
+ icount = env.instances_count
70
+ iname = (icount == 1) ? "instance" : "instances"
71
+
72
+ default_text = env.name == default_env_name ? " [default]" : ""
73
+
74
+ puts " #{short_name}#{default_text} (#{icount} #{iname})"
75
+ end
76
+ else
77
+ puts " (This application is not in any environments; you can make one at #{EY.config.endpoint})"
78
+ end
79
+
80
+ puts ""
71
81
  end
72
- print_table(printable_envs, :ident => 2)
73
82
  end
74
83
 
75
84
  def print_exception(e)
@@ -0,0 +1,5 @@
1
+ module EY
2
+ module Collection
3
+ autoload :Environments, 'engineyard/collection/environments'
4
+ end
5
+ end
@@ -0,0 +1,36 @@
1
+ require 'engineyard/error'
2
+
3
+ module EY
4
+ module Collection
5
+ class Environments < Array
6
+
7
+ def named(name)
8
+ find {|e| e.name == name}
9
+ end
10
+
11
+ def named!(name)
12
+ named(name) or raise EnvironmentError,
13
+ "Environment '#{name}' can't be found\nYou can create it at #{EY.config.endpoint}"
14
+ end
15
+
16
+ def match_one(name_part)
17
+ named(name_part) || find_by_unambiguous_substring(name_part)
18
+ end
19
+
20
+ def match_one!(name_part)
21
+ match_one(name_part) or raise EnvironmentError,
22
+ "Environment containing '#{name_part}' can't be found\nYou can create it at #{EY.config.endpoint}"
23
+ end
24
+
25
+ private
26
+ def find_by_unambiguous_substring(name_part)
27
+ candidates = find_all{|e| e.name[name_part] }
28
+ if candidates.size > 1
29
+ raise AmbiguousEnvironmentName.new(name_part, candidates.map {|e| e.name})
30
+ end
31
+ candidates.first
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -44,6 +44,16 @@ module EY
44
44
  end
45
45
  end
46
46
 
47
+ class NoSingleEnvironmentError < EY::Error
48
+ def initialize(app)
49
+ @envs = app.environments
50
+ end
51
+
52
+ def message
53
+ "Unable to determine a single environment for the current application (found #{@envs.size} environments)"
54
+ end
55
+ end
56
+
47
57
  class NoEnvironmentError < EY::Error
48
58
  def initialize(env_name=nil)
49
59
  @env_name = env_name
@@ -8,12 +8,16 @@ module EY
8
8
  end
9
9
  end
10
10
 
11
- def one_and_only_environment
11
+ def sole_environment
12
12
  if environments.size == 1
13
13
  environments.first
14
14
  end
15
15
  end
16
16
 
17
+ def sole_environment!
18
+ sole_environment or raise NoSingleEnvironmentError.new(self)
19
+ end
20
+
17
21
  end
18
22
  end
19
23
  end
@@ -9,6 +9,10 @@ module EY
9
9
  end
10
10
  end
11
11
 
12
+ def self.from_array(array, extras={})
13
+ Collection::Environments[*super]
14
+ end
15
+
12
16
  def logs
13
17
  Log.from_array(api_get("/environments/#{id}/logs")["logs"])
14
18
  end
@@ -48,6 +52,10 @@ module EY
48
52
  EY.config.environments[self.name]
49
53
  end
50
54
  alias_method :config, :configuration
55
+
56
+ def shorten_name_for(app)
57
+ name.gsub(/^#{Regexp.quote(app.name)}_/, '')
58
+ end
51
59
  end
52
60
  end
53
61
  end
@@ -3,7 +3,7 @@ require 'escape'
3
3
  module EY
4
4
  module Model
5
5
  class Instance < ApiStruct.new(:id, :role, :status, :amazon_id, :public_hostname, :environment)
6
- EYSD_VERSION = "~>0.3.2"
6
+ EYSD_VERSION = "~>0.3.3"
7
7
  CHECK_SCRIPT = <<-SCRIPT
8
8
  require "rubygems"
9
9
  requirement = Gem::Requirement.new("#{EYSD_VERSION}")
@@ -0,0 +1,47 @@
1
+ require 'thor'
2
+ require 'engineyard/cli/thor_fixes'
3
+
4
+ module EY
5
+ class Thor < ::Thor
6
+ def self.start(original_args=ARGV, config={})
7
+ @@original_args = original_args
8
+ super
9
+ end
10
+
11
+ no_tasks do
12
+ def subcommand_args
13
+ @@original_args[1..-1]
14
+ end
15
+
16
+ def self.subcommand(subcommand, subcommand_class)
17
+ define_method(subcommand) { |*_| subcommand_class.start(subcommand_args) }
18
+ end
19
+ end
20
+
21
+ protected
22
+
23
+ def api
24
+ @api ||= EY::CLI::API.new
25
+ end
26
+
27
+ def repo
28
+ @repo ||= EY::Repo.new
29
+ end
30
+
31
+ def fetch_environment(env_name)
32
+ if env_name.nil?
33
+ api.fetch_app_for_repo(repo).sole_environment!
34
+ else
35
+ api.environments.match_one!(env_name)
36
+ end
37
+ end
38
+
39
+ def get_apps(all_apps = false)
40
+ if all_apps
41
+ api.apps
42
+ else
43
+ [api.app_for_repo(repo)].compact
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+
3
+ describe EY::Collection::Environments do
4
+ before(:each) do
5
+ @envs = described_class.new([
6
+ EY::Model::Environment.from_hash("id" => 1234, "name" => "app_production"),
7
+ EY::Model::Environment.from_hash("id" => 4321, "name" => "app_staging"),
8
+ EY::Model::Environment.from_hash("id" => 8765, "name" => "bigapp_staging"),
9
+ ])
10
+ end
11
+
12
+ describe "#match_one" do
13
+ it "works when given an unambiguous substring" do
14
+ @envs.match_one("prod").name.should == "app_production"
15
+ end
16
+
17
+ it "raises an error when given an ambiguous substring" do
18
+ lambda {
19
+ @envs.match_one("staging")
20
+ }.should raise_error(EY::AmbiguousEnvironmentName)
21
+ end
22
+
23
+ it "returns an exact match if one exists" do
24
+ @envs.match_one("app_staging").name.should == "app_staging"
25
+ end
26
+
27
+ it "returns nil when it can't find anything" do
28
+ @envs.match_one("dev-and-production").should be_nil
29
+ end
30
+ end
31
+
32
+ describe "#match_one!" do
33
+ it "works when given an unambiguous substring" do
34
+ @envs.match_one!("prod").name.should == "app_production"
35
+ end
36
+
37
+ it "raises an error when given an ambiguous substring" do
38
+ lambda {
39
+ @envs.match_one!("staging")
40
+ }.should raise_error(EY::AmbiguousEnvironmentName)
41
+ end
42
+
43
+ it "returns an exact match if one exists" do
44
+ @envs.match_one!("app_staging").name.should == "app_staging"
45
+ end
46
+
47
+ it "raises an error when it can't find anything" do
48
+ lambda {
49
+ @envs.match_one!("dev-and-production")
50
+ }.should raise_error(EY::EnvironmentError)
51
+ end
52
+ end
53
+
54
+ describe "#named" do
55
+ it "finds the environment with the matching name" do
56
+ @envs.named("app_staging").id.should == 4321
57
+ end
58
+
59
+ it "returns nil when no name matches" do
60
+ @envs.named("something else").should be_nil
61
+ end
62
+ end
63
+
64
+ describe "#named!" do
65
+ it "finds the environment with the matching name" do
66
+ @envs.named!("app_staging").id.should == 4321
67
+ end
68
+
69
+ it "raises an error when no name matches" do
70
+ lambda {
71
+ @envs.named!("something else")
72
+ }.should raise_error(EY::EnvironmentError)
73
+ end
74
+ end
75
+ end
@@ -19,6 +19,19 @@ describe "EY::Model::Environment#rebuild" do
19
19
  end
20
20
  end
21
21
 
22
+ describe "EY::Model::Environment.from_array" do
23
+ it "returns a smart collection, not just a dumb array" do
24
+ api_data = [
25
+ {"id" => 32340, "name" => 'iceberg'},
26
+ {"id" => 9433, "name" => 'zoidberg'},
27
+ ]
28
+
29
+ collection = EY::Model::Environment.from_array(api_data)
30
+ collection.should be_kind_of(Array)
31
+ collection.should respond_to(:match_one)
32
+ end
33
+ end
34
+
22
35
  describe "EY::Model::Environment#instances" do
23
36
  it_should_behave_like "it has an api"
24
37
 
@@ -43,3 +56,27 @@ describe "EY::Model::Environment#instances" do
43
56
  env.instances.first.should == EY::Model::Instance.from_hash(instance_data.merge(:environment => env))
44
57
  end
45
58
  end
59
+
60
+ describe "EY::Model::Environment#shorten_name_for(app)" do
61
+ def short(environment_name, app_name)
62
+ env = EY::Model::Environment.from_hash({:name => environment_name})
63
+ app = EY::Model::App.from_hash({:name => app_name})
64
+ env.shorten_name_for(app)
65
+ end
66
+
67
+ it "turns myapp+myapp_production to production" do
68
+ short('myapp_production', 'myapp').should == 'production'
69
+ end
70
+
71
+ it "turns product+production to product (leaves it alone)" do
72
+ short('production', 'product').should == 'production'
73
+ end
74
+
75
+ it "leaves the environment name alone when the app name appears in the middle" do
76
+ short('hattery', 'ate').should == 'hattery'
77
+ end
78
+
79
+ it "does not produce an empty string when the names are the same" do
80
+ short('dev', 'dev').should == 'dev'
81
+ end
82
+ end
@@ -49,6 +49,13 @@ describe "ey deploy" do
49
49
  ey "deploy", :expect_failure => true
50
50
  @err.should match(/was called incorrectly/i)
51
51
  end
52
+
53
+ it "complains when the app master is in a non-running state" do
54
+ api_scenario "one app, one environment, app master red"
55
+ ey "deploy giblets master", :expect_failure => true
56
+ @err.should_not match(/No running instances/i)
57
+ @err.should match(/running.*\(green\)/)
58
+ end
52
59
  end
53
60
 
54
61
  it "runs when environment is known" do
@@ -88,12 +95,7 @@ describe "ey deploy" do
88
95
 
89
96
  context "choosing something to deploy" do
90
97
  before(:all) do
91
- api_scenario "one app, one environment"
92
- api_git_remote("user@git.host/path/to/repo.git")
93
- end
94
-
95
- after(:all) do
96
- api_git_remote(nil)
98
+ api_scenario "one app, one environment", "user@git.host/path/to/repo.git"
97
99
  end
98
100
 
99
101
  before(:all) do
@@ -15,7 +15,6 @@ describe "ey environments" do
15
15
  end
16
16
 
17
17
  it "reports failure to find a git repo when not in one" do
18
- api_git_remote('dontcare')
19
18
  Dir.chdir("/tmp") do
20
19
  ey "environments", :expect_failure => true
21
20
  @err.should =~ /fatal: No git remotes found in .*\/tmp/
@@ -10,6 +10,20 @@ describe "ey logs" do
10
10
  @out.should match(/CUSTOM LOG OUTPUT/)
11
11
  @err.should be_empty
12
12
  end
13
+
14
+ it "can infer the environment" do
15
+ api_scenario "one app, one environment"
16
+ ey "logs"
17
+ @out.should match(/MAIN LOG OUTPUT/)
18
+ @out.should match(/CUSTOM LOG OUTPUT/)
19
+ @err.should be_empty
20
+ end
21
+
22
+ it "complains when it can't infer the environment" do
23
+ api_scenario "one app, two environments"
24
+ ey "logs", :expect_failure => true
25
+ @err.should =~ /single environment/
26
+ end
13
27
  end
14
28
 
15
29
  describe "ey logs ENV" do
@@ -19,7 +19,7 @@ describe "ey rebuild" do
19
19
 
20
20
  it "fails when the environment name is bogus" do
21
21
  ey "rebuild typo", :expect_failure => true
22
- @err.should match(/No environment named 'typo'/)
22
+ @err.should match(/'typo'/)
23
23
  end
24
24
  end
25
25
 
@@ -1,5 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
+ print_my_args_ssh = "#!/bin/sh\necho ssh $*"
4
+
3
5
  describe "ey ssh" do
4
6
  it_should_behave_like "an integration test"
5
7
 
@@ -8,9 +10,7 @@ describe "ey ssh" do
8
10
  end
9
11
 
10
12
  it "SSH-es into the right environment" do
11
- print_my_args = "#!/bin/sh\necho ssh $*"
12
-
13
- ey "ssh giblets", :prepend_to_path => {'ssh' => print_my_args}
13
+ ey "ssh giblets", :prepend_to_path => {'ssh' => print_my_args_ssh}
14
14
  @raw_ssh_commands.should == ["ssh turkey@174.129.198.124"]
15
15
  end
16
16
 
@@ -20,11 +20,27 @@ describe "ey ssh" do
20
20
  end
21
21
 
22
22
  it "complains if you give it a bogus environment" do
23
- print_my_args = "#!/bin/sh\necho ssh $*"
24
-
25
- ey "ssh bogusenv", :prepend_to_path => {'ssh' => print_my_args}, :hide_err => true
23
+ ey "ssh bogusenv", :prepend_to_path => {'ssh' => print_my_args_ssh}, :expect_failure => true
26
24
  @raw_ssh_commands.should be_empty
27
- @out.should =~ /could not find.*bogusenv/i
25
+ @err.should =~ /bogusenv/
26
+ end
27
+ end
28
+
29
+ describe "ey ssh" do
30
+ it_should_behave_like "an integration test"
31
+
32
+ it "guesses the environment from the current application" do
33
+ api_scenario "one app, one environment"
34
+
35
+ ey "ssh", :prepend_to_path => {'ssh' => print_my_args_ssh}
36
+ @raw_ssh_commands.should == ["ssh turkey@174.129.198.124"]
37
+ end
38
+
39
+ it "complains when it can't guess the environment and its name isn't specified" do
40
+ api_scenario "one app, one environment, not linked"
41
+
42
+ ey "ssh", :prepend_to_path => {'ssh' => print_my_args_ssh}, :expect_failure => true
43
+ @err.should =~ /single environment/i
28
44
  end
29
45
  end
30
46
 
@@ -1,21 +1,54 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe "ey upload_recipes" do
3
+ describe "ey recipes upload" do
4
4
  it_should_behave_like "an integration test"
5
5
 
6
- it "posts the recipes to the correct url" do
7
- api_scenario "one app, one environment"
8
- dir = Pathname.new("/tmp/#{$$}")
9
- dir.mkdir
10
- Dir.chdir(dir) do
11
- dir.join("cookbooks").mkdir
12
- File.open(dir.join("cookbooks/file"), "w"){|f| f << "boo" }
6
+ before(:all) do
7
+ @recipe_dir = Pathname.new("/tmp/#{$$}")
8
+ @recipe_dir.mkdir
9
+ Dir.chdir(@recipe_dir) do
10
+ @recipe_dir.join("cookbooks").mkdir
11
+ File.open(@recipe_dir.join("cookbooks/file"), "w"){|f| f << "boo" }
13
12
  `git init`
14
13
  `git add .`
15
14
  `git commit -m "OMG"`
16
- ey "upload_recipes giblets", :debug => true
15
+ `git remote add testremote user@host.tld:path/to/repo.git`
16
+ end
17
+ end
18
+
19
+ it "posts the recipes to the correct url" do
20
+ api_scenario "one app, one environment"
21
+ Dir.chdir(@recipe_dir) do
22
+ ey "recipes upload giblets", :debug => true
23
+ end
24
+
25
+ @out.should =~ /recipes uploaded successfully/i
26
+ end
27
+
28
+ it "errors correctly on bogus env name" do
29
+ api_scenario "one app, one environment"
30
+ ey "recipes upload bogusenv", :expect_failure => true
31
+
32
+ @err.should =~ /can't be found/i
33
+ end
34
+
35
+ it "can infer the environment from the current application" do
36
+ api_scenario "one app, one environment", "user@host.tld:path/to/repo.git"
37
+
38
+ Dir.chdir(@recipe_dir) do
39
+ ey "recipes upload", :debug => true
17
40
  end
18
41
 
19
42
  @out.should =~ /recipes uploaded successfully/i
20
43
  end
44
+
45
+ it "complains when it can't infer the environment from the current application" do
46
+ api_scenario "one app, one environment, not linked", "user@host.tld:path/to/repo.git"
47
+
48
+ Dir.chdir(@recipe_dir) do
49
+ ey "recipes upload", :debug => true, :expect_failure => true
50
+ end
51
+
52
+ @err.should =~ /single environment/i
53
+ end
21
54
  end
@@ -66,10 +66,6 @@ shared_examples_for "an integration test without an eyrc file" do
66
66
  ENV['CLOUD_URL'] = EY.fake_awsm
67
67
  end
68
68
 
69
- before(:each) do
70
- api_git_remote nil
71
- end
72
-
73
69
  after(:all) do
74
70
  ENV.delete('CLOUD_URL')
75
71
  ENV.delete('EYRC')
@@ -28,26 +28,18 @@ class FakeAwsm < Sinatra::Base
28
28
  Scenario::UnlinkedApp
29
29
  when "one app, one environment"
30
30
  Scenario::LinkedApp
31
+ when "one app, one environment, app master red"
32
+ Scenario::LinkedAppRedMaster
31
33
  when "one app, two environments"
32
34
  Scenario::OneAppTwoEnvs
33
35
  when "one app, many similarly-named environments"
34
36
  Scenario::OneAppManySimilarlyNamedEnvs
37
+ else
38
+ status(400)
39
+ return {"ok" => "false", "message" => "wtf is the #{params[:scenario]} scenario?"}.to_json
35
40
  end
36
- if new_scenario
37
- @@scenario = new_scenario
38
- {"ok" => "true"}.to_json
39
- else
40
- status(400)
41
- {"ok" => "false", "message" => "wtf is the #{params[:scenario]} scenario?"}.to_json
42
- end
43
- end
44
-
45
- put "/git_remote" do
46
- FindableGitRemote.remote = if (!params[:remote] || params[:remote].empty?)
47
- nil
48
- else
49
- params[:remote]
50
- end
41
+ @@scenario = new_scenario.new(params[:remote])
42
+ {"ok" => "true"}.to_json
51
43
  end
52
44
 
53
45
  get "/api/v2/apps" do
@@ -67,7 +59,8 @@ class FakeAwsm < Sinatra::Base
67
59
  end
68
60
 
69
61
  put "/api/v2/environments/:env_id/rebuild" do
70
- {}.to_json
62
+ status(204)
63
+ ""
71
64
  end
72
65
 
73
66
  post "/api/v2/authenticate" do
@@ -86,54 +79,32 @@ private
86
79
  params[:password] == "test"
87
80
  end
88
81
 
89
- module FindableGitRemote
90
- class << self
91
- attr_accessor :remote
92
- end
93
-
94
- def git_remote
95
- FindableGitRemote.remote || local_git_remote
96
- end
82
+ module Scenario
83
+ class Empty
84
+ attr_reader :git_remote
97
85
 
98
- # Since we have to find something in `git remote -v` that
99
- # corresponds to an app in cloud.ey in order to do anything, we
100
- # simulate this by faking out the API to have whatever git
101
- # remote we'll find anyway.
102
- def local_git_remote
103
- remotes = []
104
- `git remote -v`.each_line do |line|
105
- parts = line.split(/\t/)
106
- # the remote will look like
107
- # "git@github.com:engineyard/engineyard.git (fetch)\n"
108
- # so we need to chop it up a bit
109
- remotes << parts[1].gsub(/\s.*$/, "") if parts[1]
86
+ def initialize(git_remote)
87
+ @git_remote = git_remote
110
88
  end
111
- remotes.first
112
- end
113
- end
114
89
 
115
- module Scenario
116
- class Empty
117
- def self.apps
90
+ def apps
118
91
  []
119
92
  end
120
93
 
121
- def self.environments
94
+ def environments
122
95
  []
123
96
  end
124
97
  end # Empty
125
98
 
126
- class UnlinkedApp
127
- extend FindableGitRemote
128
-
129
- def self.apps
99
+ class UnlinkedApp < Empty
100
+ def apps
130
101
  [{
131
102
  "name" => "rails232app",
132
103
  "environments" => [],
133
104
  "repository_uri" => git_remote}]
134
105
  end
135
106
 
136
- def self.environments
107
+ def environments
137
108
  [{
138
109
  "ssh_username" => "turkey",
139
110
  "instances" => [{
@@ -152,10 +123,8 @@ private
152
123
  end
153
124
  end # UnlinkedApp
154
125
 
155
- class LinkedApp
156
- extend FindableGitRemote
157
-
158
- def self.apps
126
+ class LinkedApp < Empty
127
+ def apps
159
128
  [{"name" => "rails232app",
160
129
  "environments" => [{"ssh_username" => "turkey",
161
130
  "instances" => [{"public_hostname" => "174.129.198.124",
@@ -173,7 +142,7 @@ private
173
142
  "repository_uri" => git_remote}]
174
143
  end
175
144
 
176
- def self.environments
145
+ def environments
177
146
  [{
178
147
  "ssh_username" => "turkey",
179
148
  "instances" => [{
@@ -193,7 +162,7 @@ private
193
162
  "id" => 27220}}]
194
163
  end
195
164
 
196
- def self.logs(env_id)
165
+ def logs(env_id)
197
166
  [{
198
167
  "id" => env_id,
199
168
  "role" => "app_master",
@@ -203,10 +172,24 @@ private
203
172
  end
204
173
  end # LinkedApp
205
174
 
206
- class OneAppTwoEnvs
207
- extend FindableGitRemote
175
+ class LinkedAppRedMaster < LinkedApp
176
+ def apps
177
+ apps = super
178
+ apps[0]["environments"][0]["instances"][0]["status"] = "error"
179
+ apps[0]["environments"][0]["app_master"]["status"] = "error"
180
+ apps
181
+ end
208
182
 
209
- def self.apps
183
+ def environments
184
+ envs = super
185
+ envs[0]["instances"][0]["status"] = "error"
186
+ envs[0]["app_master"]["status"] = "error"
187
+ envs
188
+ end
189
+ end
190
+
191
+ class OneAppTwoEnvs < Empty
192
+ def apps
210
193
  apps = [{
211
194
  "name" => "rails232app",
212
195
  "repository_uri" => git_remote
@@ -239,7 +222,7 @@ private
239
222
  "repository_uri" => git_remote}]
240
223
  end
241
224
 
242
- def self.environments
225
+ def environments
243
226
  [{
244
227
  "ssh_username" => "turkey",
245
228
  "instances" => [{
@@ -272,10 +255,8 @@ private
272
255
  end
273
256
  end # OneAppTwoEnvs
274
257
 
275
- class OneAppManySimilarlyNamedEnvs
276
- extend FindableGitRemote
277
-
278
- def self.apps
258
+ class OneAppManySimilarlyNamedEnvs < Empty
259
+ def apps
279
260
  apps = [{
280
261
  "name" => "rails232app",
281
262
  "repository_uri" => git_remote
@@ -336,7 +317,7 @@ private
336
317
  "repository_uri" => git_remote}]
337
318
  end
338
319
 
339
- def self.environments
320
+ def environments
340
321
  [{
341
322
  "ssh_username" => "turkey",
342
323
  "instances" => [{
@@ -395,7 +376,7 @@ private
395
376
  }]
396
377
  end
397
378
 
398
- def self.logs(env_id)
379
+ def logs(env_id)
399
380
  [{
400
381
  "id" => env_id,
401
382
  "role" => "app_master",
@@ -1,4 +1,4 @@
1
- require 'ey_merkin'
1
+ require 'realweb'
2
2
  require "rest_client"
3
3
  require 'open4'
4
4
 
@@ -42,8 +42,8 @@ module Spec
42
42
  with_env(ey_env) do
43
43
  exit_status = Open4::open4("#{eybin} #{cmd}") do |pid, stdin, stdout, stderr|
44
44
  block.call(stdin) if block
45
- @err = stderr.read_available_bytes
46
- @out = stdout.read_available_bytes
45
+ @err = stderr.read
46
+ @out = stdout.read
47
47
  end
48
48
 
49
49
  if !exit_status.success? && !options[:expect_failure]
@@ -75,14 +75,21 @@ module Spec
75
75
  @out
76
76
  end
77
77
 
78
- def api_scenario(scenario)
79
- response = ::RestClient.put(EY.fake_awsm + '/scenario', {"scenario" => scenario}, {})
78
+ def api_scenario(scenario, remote = local_git_remote)
79
+ response = ::RestClient.put(EY.fake_awsm + '/scenario', {"scenario" => scenario, "remote" => remote}, {})
80
80
  raise "Setting scenario failed: #{response.inspect}" unless response.code == 200
81
81
  end
82
82
 
83
- def api_git_remote(remote)
84
- response = ::RestClient.put(EY.fake_awsm + '/git_remote', {"remote" => remote}, {})
85
- raise "Setting git remote failed: #{response.inspect}" unless response.code == 200
83
+ def local_git_remote
84
+ remotes = []
85
+ `git remote -v`.each_line do |line|
86
+ parts = line.split(/\t/)
87
+ # the remote will look like
88
+ # "git@github.com:engineyard/engineyard.git (fetch)\n"
89
+ # so we need to chop it up a bit
90
+ remotes << parts[1].gsub(/\s.*$/, "") if parts[1]
91
+ end
92
+ remotes.first
86
93
  end
87
94
 
88
95
  def read_yaml(file="ey.yml")
@@ -125,7 +132,7 @@ module EY
125
132
  raise SyntaxError, "There is a syntax error in fake_awsm.ru! fix it!"
126
133
  end
127
134
  config_ru = File.join(EY_ROOT, "spec/support/fake_awsm.ru")
128
- @server = EY::Merkin.start_server(config_ru)
135
+ @server = RealWeb.start_server_in_fork(config_ru)
129
136
  "http://localhost:#{@server.port}"
130
137
  end
131
138
  end
@@ -11,19 +11,3 @@ module Kernel
11
11
  end
12
12
  alias capture_stdout capture_stdio
13
13
  end
14
-
15
- class IO
16
- def read_available_bytes(chunk_size = 1024, select_timeout = 5)
17
- buffer = []
18
-
19
- while self.class.select([self], nil, nil, select_timeout)
20
- begin
21
- buffer << self.readpartial(chunk_size)
22
- rescue(EOFError)
23
- break
24
- end
25
- end
26
-
27
- return buffer.join
28
- end
29
- end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 3
8
- - 2
9
- version: 0.3.2
8
+ - 3
9
+ version: 0.3.3
10
10
  platform: ruby
11
11
  authors:
12
12
  - EY Cloud Team
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-05-26 00:00:00 -07:00
17
+ date: 2010-06-03 00:00:00 -07:00
18
18
  default_executable: ey
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -111,9 +111,12 @@ files:
111
111
  - lib/engineyard/api.rb
112
112
  - lib/engineyard/cli/action/deploy.rb
113
113
  - lib/engineyard/cli/api.rb
114
+ - lib/engineyard/cli/recipes.rb
114
115
  - lib/engineyard/cli/thor_fixes.rb
115
116
  - lib/engineyard/cli/ui.rb
116
117
  - lib/engineyard/cli.rb
118
+ - lib/engineyard/collection/environments.rb
119
+ - lib/engineyard/collection.rb
117
120
  - lib/engineyard/config.rb
118
121
  - lib/engineyard/error.rb
119
122
  - lib/engineyard/model/api_struct.rb
@@ -124,6 +127,7 @@ files:
124
127
  - lib/engineyard/model.rb
125
128
  - lib/engineyard/repo.rb
126
129
  - lib/engineyard/ruby_ext.rb
130
+ - lib/engineyard/thor.rb
127
131
  - lib/engineyard.rb
128
132
  - LICENSE
129
133
  - README.rdoc
@@ -161,6 +165,7 @@ test_files:
161
165
  - spec/engineyard/api_spec.rb
162
166
  - spec/engineyard/cli/api_spec.rb
163
167
  - spec/engineyard/cli_spec.rb
168
+ - spec/engineyard/collection/environments.rb
164
169
  - spec/engineyard/config_spec.rb
165
170
  - spec/engineyard/model/api_struct_spec.rb
166
171
  - spec/engineyard/model/environment_spec.rb