engineyard 0.2.11 → 0.2.12
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/engineyard.rb +4 -3
- data/lib/engineyard/#repo.rb# +24 -0
- data/lib/engineyard/account.rb +32 -11
- data/lib/engineyard/account/api_struct.rb +25 -0
- data/lib/engineyard/account/app.rb +11 -10
- data/lib/engineyard/account/app_master.rb +1 -7
- data/lib/engineyard/account/environment.rb +31 -16
- data/lib/engineyard/account/instance.rb +6 -0
- data/lib/engineyard/account/log.rb +7 -16
- data/lib/engineyard/action/deploy.rb +138 -0
- data/lib/engineyard/action/list_environments.rb +22 -0
- data/lib/engineyard/action/rebuild.rb +31 -0
- data/lib/engineyard/action/show_logs.rb +26 -0
- data/lib/engineyard/action/ssh.rb +19 -0
- data/lib/engineyard/action/upload_recipes.rb +17 -0
- data/lib/engineyard/action/util.rb +47 -0
- data/lib/engineyard/cli.rb +24 -150
- data/lib/engineyard/cli/thor_fixes.rb +26 -0
- data/lib/engineyard/error.rb +48 -0
- data/lib/engineyard/repo.rb +1 -1
- data/lib/engineyard/ruby_ext.rb +9 -0
- data/spec/engineyard/account/api_struct_spec.rb +37 -0
- data/spec/engineyard/account/environment_spec.rb +20 -0
- data/spec/engineyard/account_spec.rb +18 -0
- data/spec/engineyard/cli_spec.rb +3 -3
- data/spec/ey/deploy_spec.rb +166 -28
- data/spec/ey/ey_spec.rb +3 -3
- data/spec/ey/list_environments_spec.rb +16 -0
- data/spec/ey/logs_spec.rb +2 -17
- data/spec/ey/rebuild_spec.rb +25 -0
- data/spec/ey/ssh_spec.rb +24 -0
- data/spec/ey/upload_recipes_spec.rb +21 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/support/fake_awsm.ru +25 -1
- data/spec/support/helpers.rb +72 -7
- metadata +44 -56
- data/lib/engineyard/cli/error.rb +0 -44
- data/spec/spec.opts +0 -2
data/lib/engineyard.rb
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
module EY
|
2
|
-
|
2
|
+
require 'engineyard/ruby_ext'
|
3
|
+
|
4
|
+
VERSION = "0.2.12"
|
3
5
|
|
4
6
|
autoload :Account, 'engineyard/account'
|
5
7
|
autoload :API, 'engineyard/api'
|
6
8
|
autoload :Config, 'engineyard/config'
|
9
|
+
autoload :Error, 'engineyard/error'
|
7
10
|
autoload :Repo, 'engineyard/repo'
|
8
11
|
|
9
|
-
class Error < RuntimeError; end
|
10
|
-
|
11
12
|
class UI
|
12
13
|
# stub debug outside of the CLI
|
13
14
|
def debug(*); end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module EY
|
2
|
+
class Repo
|
3
|
+
|
4
|
+
def initialize(path=File.expand_path('.'))
|
5
|
+
@path = path
|
6
|
+
end
|
7
|
+
|
8
|
+
def current_branch
|
9
|
+
head = File.read(File.join(@path, ".git/HEAD")).chomp
|
10
|
+
if head.gsub!("ref: refs/heads/", "")
|
11
|
+
head
|
12
|
+
else
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def urls
|
18
|
+
`git config -f #{@path}/.git/config --get-regexp 'remote.*.url'`.split(/\n/).map do |c|
|
19
|
+
c.split.last
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end # Repo
|
24
|
+
end # EY
|
data/lib/engineyard/account.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
+
require 'engineyard/account/api_struct'
|
1
2
|
require 'engineyard/account/app'
|
2
3
|
require 'engineyard/account/app_master'
|
3
4
|
require 'engineyard/account/environment'
|
4
5
|
require 'engineyard/account/log'
|
6
|
+
require 'engineyard/account/instance'
|
5
7
|
|
6
8
|
module EY
|
7
9
|
class Account
|
@@ -10,24 +12,43 @@ module EY
|
|
10
12
|
@api = api
|
11
13
|
end
|
12
14
|
|
13
|
-
def
|
14
|
-
@
|
15
|
+
def environments
|
16
|
+
@environments ||= begin
|
17
|
+
data = @api.request('/environments')["environments"]
|
18
|
+
Environment.from_array(data, self)
|
19
|
+
end
|
15
20
|
end
|
16
21
|
|
17
|
-
def
|
18
|
-
|
19
|
-
data = request('/environments')["environments"]
|
20
|
-
@environments = Environment.from_array(data || [], self)
|
22
|
+
def apps
|
23
|
+
@apps ||= App.from_array(@api.request('/apps')["apps"], self)
|
21
24
|
end
|
22
25
|
|
23
26
|
def environment_named(name)
|
24
27
|
environments.find{|e| e.name == name }
|
25
28
|
end
|
26
29
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
30
|
+
def logs_for(env)
|
31
|
+
data = @api.request("/environments/#{env.id}/logs")["logs"]
|
32
|
+
Log.from_array(data)
|
33
|
+
end
|
34
|
+
|
35
|
+
def instances_for(env)
|
36
|
+
@instances ||= begin
|
37
|
+
data = @api.request("/environments/#{env.id}/instances")["instances"]
|
38
|
+
Instance.from_array(data)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def upload_recipes_for(env)
|
43
|
+
@api.request("/environments/#{env.id}/recipes",
|
44
|
+
:method => :post,
|
45
|
+
:params => {:file => env.recipe_file}
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
def rebuild(env)
|
50
|
+
@api.request("/environments/#{env.id}/rebuild",
|
51
|
+
:method => :put)
|
31
52
|
end
|
32
53
|
|
33
54
|
def app_named(name)
|
@@ -35,7 +56,7 @@ module EY
|
|
35
56
|
end
|
36
57
|
|
37
58
|
def app_for_repo(repo)
|
38
|
-
apps.find{|a| repo.urls.include?(a.
|
59
|
+
apps.find{|a| repo.urls.include?(a.repository_uri) }
|
39
60
|
end
|
40
61
|
|
41
62
|
end # Account
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module EY
|
2
|
+
class Account
|
3
|
+
class ApiStruct < Struct
|
4
|
+
def self.new(*args, &block)
|
5
|
+
super(*args) do |*block_args|
|
6
|
+
block.call(*block_args) if block
|
7
|
+
|
8
|
+
def self.from_array(array, account = nil)
|
9
|
+
array.map do |values|
|
10
|
+
from_hash(values.merge(:account => account))
|
11
|
+
end if array
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.from_hash(hash)
|
15
|
+
return nil unless hash
|
16
|
+
members = new.members
|
17
|
+
values = members.map{|a| hash.has_key?(a.to_sym) ? hash[a.to_sym] : hash[a] }
|
18
|
+
new(*values)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,18 +1,19 @@
|
|
1
1
|
module EY
|
2
2
|
class Account
|
3
|
-
class App <
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
account
|
10
|
-
) if hash && hash != "null"
|
3
|
+
class App < ApiStruct.new(:name, :repository_uri, :environments, :account)
|
4
|
+
|
5
|
+
def self.from_hash(hash)
|
6
|
+
super.tap do |app|
|
7
|
+
app.environments = Environment.from_array(app.environments, app.account)
|
8
|
+
end
|
11
9
|
end
|
12
10
|
|
13
|
-
def
|
14
|
-
|
11
|
+
def one_and_only_environment
|
12
|
+
if environments.size == 1
|
13
|
+
environments.first
|
14
|
+
end
|
15
15
|
end
|
16
|
+
|
16
17
|
end
|
17
18
|
end
|
18
19
|
end
|
@@ -1,12 +1,6 @@
|
|
1
1
|
module EY
|
2
2
|
class Account
|
3
|
-
class AppMaster <
|
4
|
-
def self.from_hash(hash)
|
5
|
-
new(
|
6
|
-
hash["status"],
|
7
|
-
hash["public_hostname"]
|
8
|
-
) if hash && hash != "null"
|
9
|
-
end
|
3
|
+
class AppMaster < ApiStruct.new(:status, :public_hostname)
|
10
4
|
end
|
11
5
|
end
|
12
6
|
end
|
@@ -1,25 +1,40 @@
|
|
1
1
|
module EY
|
2
2
|
class Account
|
3
|
-
class Environment <
|
4
|
-
def self.from_hash(hash
|
5
|
-
|
6
|
-
hash[
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
AppMaster.from_hash(hash["app_master"]),
|
11
|
-
hash["ssh_username"],
|
12
|
-
account
|
13
|
-
) if hash && hash != "null"
|
3
|
+
class Environment < ApiStruct.new(:id, :name, :instances_count, :apps, :app_master, :username, :account)
|
4
|
+
def self.from_hash(hash)
|
5
|
+
super.tap do |env|
|
6
|
+
env.username = hash['ssh_username']
|
7
|
+
env.apps = App.from_array(env.apps, env.account)
|
8
|
+
env.app_master = AppMaster.from_hash(env.app_master)
|
9
|
+
end
|
14
10
|
end
|
15
11
|
|
16
|
-
def
|
17
|
-
|
12
|
+
def logs
|
13
|
+
account.logs_for(self)
|
18
14
|
end
|
19
15
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
16
|
+
def instances
|
17
|
+
account.instances_for(self)
|
18
|
+
end
|
19
|
+
|
20
|
+
def rebuild
|
21
|
+
account.rebuild(self)
|
22
|
+
end
|
23
|
+
|
24
|
+
def recipe_file
|
25
|
+
require 'tempfile'
|
26
|
+
unless File.exist?("cookbooks")
|
27
|
+
raise EY::Error, "Could not find chef recipes. Please run from the root of your recipes repo."
|
28
|
+
end
|
29
|
+
|
30
|
+
tmp = Tempfile.new("recipes")
|
31
|
+
cmd = "git archive --format=tar HEAD cookbooks | gzip > #{tmp.path}"
|
32
|
+
|
33
|
+
unless system(cmd)
|
34
|
+
raise EY::Error, "Could not archive recipes.\nCommand `#{cmd}` exited with an error."
|
35
|
+
end
|
36
|
+
|
37
|
+
tmp
|
23
38
|
end
|
24
39
|
|
25
40
|
def configuration
|
@@ -1,18 +1,9 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
new(
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
) if hash && hash != "null"
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.from_array(array)
|
12
|
-
array.map{|n| from_hash(n) } if array && array != "null"
|
13
|
-
end
|
14
|
-
|
15
|
-
def instance_name
|
16
|
-
"#{role} #{id}"
|
1
|
+
module EY
|
2
|
+
class Account
|
3
|
+
class Log < ApiStruct.new(:id, :role, :main, :custom)
|
4
|
+
def instance_name
|
5
|
+
"#{role} #{id}"
|
6
|
+
end
|
7
|
+
end
|
17
8
|
end
|
18
9
|
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'engineyard/action/util'
|
2
|
+
|
3
|
+
module EY
|
4
|
+
module Action
|
5
|
+
class Deploy
|
6
|
+
extend Util
|
7
|
+
|
8
|
+
EYSD_VERSION = "~>0.2.7"
|
9
|
+
|
10
|
+
def self.call(env_name, branch, options)
|
11
|
+
env_name ||= EY.config.default_environment
|
12
|
+
|
13
|
+
app = fetch_app
|
14
|
+
env = fetch_environment(env_name, app)
|
15
|
+
branch = fetch_branch(env.name, branch, options[:force])
|
16
|
+
|
17
|
+
running = env.app_master && env.app_master.status == "running"
|
18
|
+
raise EnvironmentError, "No running instances for environment #{env.name}\nStart one at #{EY.config.endpoint}" unless running
|
19
|
+
|
20
|
+
hostname = env.app_master.public_hostname
|
21
|
+
username = env.username
|
22
|
+
|
23
|
+
EY.ui.info "Connecting to the server..."
|
24
|
+
ensure_eysd_present(hostname, username, options[:install_eysd])
|
25
|
+
|
26
|
+
deploy_cmd = "#{eysd_path} deploy --app #{app.name} --branch #{branch}"
|
27
|
+
if env.config
|
28
|
+
escaped_config_option = env.config.to_json.gsub(/"/, "\\\"")
|
29
|
+
deploy_cmd << " --config '#{escaped_config_option}'"
|
30
|
+
end
|
31
|
+
|
32
|
+
if options['migrate']
|
33
|
+
deploy_cmd << " --migrate='#{options[:migrate]}'"
|
34
|
+
end
|
35
|
+
|
36
|
+
EY.ui.info "Running deploy on server..."
|
37
|
+
deployed = ssh_to(hostname, deploy_cmd, username)
|
38
|
+
|
39
|
+
if deployed
|
40
|
+
EY.ui.info "Deploy complete"
|
41
|
+
else
|
42
|
+
raise EY::Error, "Deploy failed"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def self.fetch_app
|
49
|
+
app = account.app_for_repo(repo)
|
50
|
+
raise NoAppError.new(repo) unless app
|
51
|
+
app
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.fetch_environment(env_name, app)
|
55
|
+
# if the name's not specified and there's not exactly one
|
56
|
+
# environment, we can't figure out which environment to deploy
|
57
|
+
raise DeployArgumentError if !env_name && app.environments.size != 1
|
58
|
+
|
59
|
+
env = if env_name
|
60
|
+
# environment names are unique per-customer, so
|
61
|
+
# there's no danger of finding two here
|
62
|
+
app.environments.find{|e| e.name == env_name }
|
63
|
+
else
|
64
|
+
app.environments.first
|
65
|
+
end
|
66
|
+
|
67
|
+
# the environment exists, but doesn't have this app
|
68
|
+
if !env && account.environment_named(env_name)
|
69
|
+
raise EnvironmentError, "Environment '#{env_name}' doesn't run this application\nYou can add it at #{EY.config.endpoint}"
|
70
|
+
end
|
71
|
+
|
72
|
+
if !env
|
73
|
+
raise NoEnvironmentError.new(env_name)
|
74
|
+
end
|
75
|
+
|
76
|
+
env
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.fetch_branch(env_name, user_specified_branch, force)
|
80
|
+
default_branch = EY.config.default_branch(env_name)
|
81
|
+
|
82
|
+
branch = if user_specified_branch
|
83
|
+
if default_branch && (user_specified_branch != default_branch) && !force
|
84
|
+
raise BranchMismatch.new(default_branch, user_specified_branch)
|
85
|
+
end
|
86
|
+
user_specified_branch
|
87
|
+
else
|
88
|
+
default_branch || repo.current_branch
|
89
|
+
end
|
90
|
+
|
91
|
+
raise DeployArgumentError unless branch
|
92
|
+
branch
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.ensure_eysd_present(hostname, username, install_eysd)
|
96
|
+
ssh_to(hostname, "#{eysd_path} check '#{EY::VERSION}' '#{EYSD_VERSION}'", username, false)
|
97
|
+
case $?.exitstatus
|
98
|
+
when 255
|
99
|
+
raise EnvironmentError, "SSH connection to #{hostname} failed"
|
100
|
+
when 127
|
101
|
+
EY.ui.warn "Server does not have ey-deploy gem installed"
|
102
|
+
eysd_installed = false
|
103
|
+
when 0
|
104
|
+
eysd_installed = true
|
105
|
+
else
|
106
|
+
raise EnvironmentError, "ey-deploy version not compatible"
|
107
|
+
end
|
108
|
+
|
109
|
+
if !eysd_installed || install_eysd
|
110
|
+
EY.ui.info "Installing ey-deploy gem..."
|
111
|
+
ssh_to(hostname,
|
112
|
+
"sudo #{gem_path} install ey-deploy -v '#{EYSD_VERSION}'",
|
113
|
+
username)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def self.eysd_path
|
118
|
+
"/usr/local/ey_resin/ruby/bin/eysd"
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.gem_path
|
122
|
+
"/usr/local/ey_resin/ruby/bin/gem"
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.ssh_to(hostname, remote_cmd, user, output = true)
|
126
|
+
cmd = %{ssh -o StrictHostKeyChecking=no -q #{user}@#{hostname} "#{remote_cmd}"}
|
127
|
+
cmd << %{ &> /dev/null} unless output
|
128
|
+
output ? puts(cmd) : EY.ui.debug(cmd)
|
129
|
+
unless ENV["NO_SSH"]
|
130
|
+
system cmd
|
131
|
+
else
|
132
|
+
true
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'engineyard/action/util'
|
2
|
+
|
3
|
+
module EY
|
4
|
+
module Action
|
5
|
+
class ListEnvironments
|
6
|
+
extend Util
|
7
|
+
|
8
|
+
def self.call(all)
|
9
|
+
app, envs = app_and_envs(all)
|
10
|
+
if app
|
11
|
+
EY.ui.say %|Cloud environments for #{app.name}:|
|
12
|
+
EY.ui.print_envs(envs, EY.config.default_environment)
|
13
|
+
elsif envs
|
14
|
+
EY.ui.say %|Cloud environments:|
|
15
|
+
EY.ui.print_envs(envs, EY.config.default_environment)
|
16
|
+
else
|
17
|
+
EY.ui.say %|You do not have any cloud environments.|
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'engineyard/action/util'
|
2
|
+
|
3
|
+
module EY
|
4
|
+
module Action
|
5
|
+
class Rebuild
|
6
|
+
extend Util
|
7
|
+
|
8
|
+
def self.call(name)
|
9
|
+
env = fetch_environment_by_name(name) || fetch_environment_from_app
|
10
|
+
EY.ui.debug("Rebuilding #{env.name}")
|
11
|
+
env.rebuild
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
def self.fetch_environment_by_name(name)
|
16
|
+
if name
|
17
|
+
env = account.environment_named(name)
|
18
|
+
return env if env
|
19
|
+
raise NoEnvironmentError.new(name)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.fetch_environment_from_app
|
24
|
+
repo = Repo.new
|
25
|
+
app = account.app_for_repo(repo) or raise NoAppError.new(repo)
|
26
|
+
env = app.one_and_only_environment or raise EnvironmentError, "Unable to determine a single environment for the current application (found #{app.environments.size} environments)"
|
27
|
+
env
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|