engineyard 0.3.2 → 0.3.3

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.
@@ -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