engineyard 0.2.11 → 0.2.12
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.
- 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
|