ey-core 3.1.2 → 3.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.ruby-version +1 -1
- data/.travis.yml +1 -0
- data/Gemfile +0 -2
- data/examples/add_instance.rb +74 -0
- data/examples/boot_env.rb +60 -0
- data/examples/stop_env.rb +51 -0
- data/examples/terminate_instance.rb +58 -0
- data/lib/ey-core/cli/accounts.rb +14 -6
- data/lib/ey-core/cli/applications.rb +32 -12
- data/lib/ey-core/cli/console.rb +24 -10
- data/lib/ey-core/cli/current_user.rb +13 -5
- data/lib/ey-core/cli/deploy.rb +110 -52
- data/lib/ey-core/cli/environments.rb +34 -12
- data/lib/ey-core/cli/errors.rb +10 -6
- data/lib/ey-core/cli/help.rb +30 -0
- data/lib/ey-core/cli/helpers/archive.rb +70 -0
- data/lib/ey-core/cli/helpers/chef.rb +35 -0
- data/lib/ey-core/cli/helpers/core.rb +195 -0
- data/lib/ey-core/cli/helpers/deprecated.rb +39 -0
- data/lib/ey-core/cli/helpers/log_streaming.rb +41 -0
- data/lib/ey-core/cli/helpers/stream_printer.rb +42 -0
- data/lib/ey-core/cli/init.rb +11 -8
- data/lib/ey-core/cli/login.rb +33 -21
- data/lib/ey-core/cli/logout.rb +18 -10
- data/lib/ey-core/cli/logs.rb +57 -35
- data/lib/ey-core/cli/main.rb +52 -15
- data/lib/ey-core/cli/recipes.rb +5 -87
- data/lib/ey-core/cli/recipes/apply.rb +83 -43
- data/lib/ey-core/cli/recipes/download.rb +48 -22
- data/lib/ey-core/cli/recipes/main.rb +21 -0
- data/lib/ey-core/cli/recipes/upload.rb +56 -23
- data/lib/ey-core/cli/scp.rb +11 -8
- data/lib/ey-core/cli/servers.rb +37 -15
- data/lib/ey-core/cli/ssh.rb +127 -70
- data/lib/ey-core/cli/status.rb +54 -14
- data/lib/ey-core/cli/subcommand.rb +47 -108
- data/lib/ey-core/cli/timeout_deploy.rb +56 -26
- data/lib/ey-core/cli/version.rb +13 -5
- data/lib/ey-core/cli/web.rb +7 -7
- data/lib/ey-core/cli/web/disable.rb +46 -20
- data/lib/ey-core/cli/web/enable.rb +40 -17
- data/lib/ey-core/cli/web/main.rb +21 -0
- data/lib/ey-core/cli/web/restart.rb +34 -15
- data/lib/ey-core/cli/whoami.rb +11 -3
- data/lib/ey-core/mock/searching.rb +4 -0
- data/lib/ey-core/model.rb +5 -0
- data/lib/ey-core/models/deployment.rb +7 -0
- data/lib/ey-core/models/environment.rb +5 -0
- data/lib/ey-core/models/request.rb +2 -0
- data/lib/ey-core/models/user.rb +2 -0
- data/lib/ey-core/requests/get_servers.rb +1 -1
- data/lib/ey-core/response.rb +4 -0
- data/lib/ey-core/subscribable.rb +3 -3
- data/lib/ey-core/version.rb +1 -1
- data/spec/ey-core/cli/accounts_spec.rb +20 -0
- data/spec/ey-core/cli/recipes/apply_spec.rb +4 -17
- data/spec/ey-core/cli/recipes/download_spec.rb +93 -0
- data/spec/ey-core/cli/recipes/upload_spec.rb +80 -0
- data/spec/servers_spec.rb +15 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/support/cli_helpers.rb +38 -2
- metadata +116 -53
- checksums.yaml +0 -7
@@ -1,17 +1,39 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
summary "Retrieve a list of Engine Yard environments that you have access to."
|
4
|
-
option :account, short: 'c', long: 'account', description: 'Filter by account name or id', argument: 'Account'
|
1
|
+
require 'ey-core/cli/subcommand'
|
2
|
+
require 'ey-core/cli/helpers/stream_printer'
|
5
3
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
4
|
+
module Ey
|
5
|
+
module Core
|
6
|
+
module Cli
|
7
|
+
class Environments < Subcommand
|
8
|
+
include Ey::Core::Cli::Helpers::StreamPrinter
|
9
|
+
title "environments"
|
10
|
+
summary "Retrieve a list of Engine Yard environments that you have access to."
|
12
11
|
|
12
|
+
option :account,
|
13
|
+
short: 'c',
|
14
|
+
long: 'account',
|
15
|
+
description: 'Filter by account name or id',
|
16
|
+
argument: 'Account'
|
13
17
|
|
14
|
-
|
15
|
-
|
18
|
+
def handle
|
19
|
+
if option(:account)
|
20
|
+
stream_print("ID" => 10, "Name" => 50) do |printer|
|
21
|
+
core_account.environments.each_entry do |env|
|
22
|
+
printer.print(env.id, env.name)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
else
|
26
|
+
stream_print("ID" => 10, "Name" => 50, "Account" => 50) do |printer|
|
27
|
+
core_accounts.each_entry do |account|
|
28
|
+
account.environments.each_entry do |env|
|
29
|
+
printer.print(env.id, env.name, account.name)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
16
38
|
end
|
17
39
|
end
|
data/lib/ey-core/cli/errors.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
1
|
+
module Ey
|
2
|
+
module Core
|
3
|
+
module Cli
|
4
|
+
RecipesNotFound = Class.new(ArgumentError)
|
5
|
+
NoCommand = Class.new(ArgumentError)
|
6
|
+
NoRepository = Class.new(ArgumentError)
|
7
|
+
RecipesExist = Class.new(ArgumentError)
|
8
|
+
AmbiguousSearch = Class.new(ArgumentError)
|
9
|
+
end
|
10
|
+
end
|
7
11
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'belafonte'
|
2
|
+
|
3
|
+
module Ey
|
4
|
+
module Core
|
5
|
+
module Cli
|
6
|
+
class Help < Belafonte::App
|
7
|
+
title 'help'
|
8
|
+
summary 'Print out the help docs'
|
9
|
+
|
10
|
+
arg :command_line,
|
11
|
+
times: :unlimited
|
12
|
+
|
13
|
+
def handle
|
14
|
+
Main.new(
|
15
|
+
command_line.unshift('-h'),
|
16
|
+
stdin,
|
17
|
+
stdout,
|
18
|
+
stderr,
|
19
|
+
kernel
|
20
|
+
).execute!
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def command_line
|
25
|
+
arg(:command_line)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
require 'rubygems/package'
|
3
|
+
require 'zlib'
|
4
|
+
|
5
|
+
module Ey
|
6
|
+
module Core
|
7
|
+
module Cli
|
8
|
+
module Helpers
|
9
|
+
module Archive
|
10
|
+
def gzip(tarfile)
|
11
|
+
gz = StringIO.new("")
|
12
|
+
zipper = Zlib::GzipWriter.new(gz)
|
13
|
+
zipper.write tarfile.string
|
14
|
+
zipper.close # this is necessary!
|
15
|
+
|
16
|
+
# z was closed to write the gzip footer, so
|
17
|
+
# now we need a new StringIO
|
18
|
+
StringIO.new gz.string
|
19
|
+
end
|
20
|
+
|
21
|
+
def archive_directory(path)
|
22
|
+
tarfile = StringIO.new("")
|
23
|
+
Gem::Package::TarWriter.new(tarfile) do |tar|
|
24
|
+
Dir[File.join(path, "**/*")].each do |file|
|
25
|
+
mode = File.stat(file).mode
|
26
|
+
relative_file = "cookbooks/#{file.sub(/^#{Regexp::escape path}\/?/, '')}"
|
27
|
+
|
28
|
+
if File.directory?(file)
|
29
|
+
tar.mkdir relative_file, mode
|
30
|
+
else
|
31
|
+
tar.add_file relative_file, mode do |tf|
|
32
|
+
File.open(file, "rb") { |f| tf.write f.read }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
tarfile.rewind
|
39
|
+
gzip(tarfile)
|
40
|
+
end
|
41
|
+
|
42
|
+
def ungzip(tarfile)
|
43
|
+
zipped = Zlib::GzipReader.new(tarfile)
|
44
|
+
unzipped = StringIO.new(zipped.read)
|
45
|
+
zipped.close
|
46
|
+
unzipped
|
47
|
+
end
|
48
|
+
|
49
|
+
def untar(io, destination)
|
50
|
+
Gem::Package::TarReader.new io do |tar|
|
51
|
+
tar.each do |tarfile|
|
52
|
+
destination_file = File.join destination, tarfile.full_name
|
53
|
+
|
54
|
+
if tarfile.directory?
|
55
|
+
FileUtils.mkdir_p destination_file
|
56
|
+
else
|
57
|
+
destination_directory = File.dirname(destination_file)
|
58
|
+
FileUtils.mkdir_p destination_directory unless File.directory?(destination_directory)
|
59
|
+
File.open destination_file, "wb" do |file|
|
60
|
+
file.print tarfile.read
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'ey-core/cli/errors'
|
2
|
+
|
3
|
+
module Ey
|
4
|
+
module Core
|
5
|
+
module Cli
|
6
|
+
module Helpers
|
7
|
+
module Chef
|
8
|
+
def run_chef(type, environment)
|
9
|
+
request = environment.apply(type)
|
10
|
+
puts "Started #{type} chef run".green
|
11
|
+
request.wait_for { |r| r.ready? }
|
12
|
+
if request.successful
|
13
|
+
puts "#{type.capitalize} chef run completed".green
|
14
|
+
else
|
15
|
+
puts "#{type.capitalize} chef run failed".red
|
16
|
+
ap request
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def upload_recipes(environment, path="cookbooks/")
|
21
|
+
recipes_path = Pathname.new(path)
|
22
|
+
|
23
|
+
if recipes_path.exist? && recipes_path.to_s.match(/\.(tgz|tar\.gz)/)
|
24
|
+
environment.upload_recipes(recipes_path)
|
25
|
+
elsif recipes_path.exist?
|
26
|
+
environment.upload_recipes(archive_directory(path))
|
27
|
+
else
|
28
|
+
raise RecipesNotFound, "Recipes file not found: #{recipes_path}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
require 'colorize'
|
2
|
+
require 'ey-core/cli/errors'
|
3
|
+
|
4
|
+
module Ey
|
5
|
+
module Core
|
6
|
+
module Cli
|
7
|
+
module Helpers
|
8
|
+
module Core
|
9
|
+
module ClassMethods
|
10
|
+
def core_file
|
11
|
+
@core_file ||= File.expand_path("~/.ey-core")
|
12
|
+
end
|
13
|
+
|
14
|
+
def eyrc
|
15
|
+
@eyrc ||= File.expand_path("~/.eyrc")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def core_url
|
20
|
+
env_url = ENV["CORE_URL"] || ENV["CLOUD_URL"]
|
21
|
+
(env_url && File.join(env_url, '/')) || "https://api.engineyard.com/"
|
22
|
+
end
|
23
|
+
|
24
|
+
def longest_length_by_name(collection)
|
25
|
+
collection.map(&:name).group_by(&:size).max.last.length
|
26
|
+
end
|
27
|
+
|
28
|
+
def core_yaml
|
29
|
+
@core_yaml ||= YAML.load_file(self.class.core_file) || {}
|
30
|
+
rescue Errno::ENOENT => e
|
31
|
+
puts "Creating #{self.class.core_file}".yellow
|
32
|
+
FileUtils.touch(self.class.core_file)
|
33
|
+
retry
|
34
|
+
end
|
35
|
+
|
36
|
+
def operator(options)
|
37
|
+
if options[:account]
|
38
|
+
core_account
|
39
|
+
elsif ENV["STAFF"]
|
40
|
+
core_client
|
41
|
+
else
|
42
|
+
core_client.users.current
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def core_operator_and_environment_for(options={})
|
47
|
+
unless options[:environment]
|
48
|
+
raise "--environment is required (for a list of environments, try `ey environments`)"
|
49
|
+
end
|
50
|
+
operator = operator(options)
|
51
|
+
environment = nil
|
52
|
+
if options[:environment].to_i.to_s == options[:environment]
|
53
|
+
environment = operator.environments.get(options[:environment])
|
54
|
+
end
|
55
|
+
unless environment
|
56
|
+
candidate_envs = operator.environments.all(name: options[:environment])
|
57
|
+
if candidate_envs.size > 1
|
58
|
+
raise "Multiple matching environments found named '#{options[:environment]}', please specify --account"
|
59
|
+
else
|
60
|
+
environment = candidate_envs.first
|
61
|
+
end
|
62
|
+
end
|
63
|
+
unless environment
|
64
|
+
raise "environment '#{options[:environment]}' not found (for a list of environments, try `ey environments`)"
|
65
|
+
end
|
66
|
+
[operator, environment]
|
67
|
+
end
|
68
|
+
|
69
|
+
def core_environment_for(options={})
|
70
|
+
core_client.environments.get(options[:environment]) || core_client.environments.first(name: options[:environment])
|
71
|
+
end
|
72
|
+
|
73
|
+
def core_server_for(options={})
|
74
|
+
operator = options.fetch(:operator, core_client)
|
75
|
+
operator.servers.get(options[:server]) || operator.servers.first(provisioned_id: options[:server])
|
76
|
+
end
|
77
|
+
|
78
|
+
def core_application_for(environment, options={})
|
79
|
+
candidate_apps = nil
|
80
|
+
unless options[:app]
|
81
|
+
candidate_apps = environment.applications.map(&:name)
|
82
|
+
if candidate_apps.size == 1
|
83
|
+
options[:app] = candidate_apps.first
|
84
|
+
else
|
85
|
+
raise "--app is required (Candidate apps on environment #{environment.name}: #{candidate_apps.join(', ')})"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
app = begin
|
90
|
+
Integer(options[:app])
|
91
|
+
rescue
|
92
|
+
options[:app]
|
93
|
+
end
|
94
|
+
|
95
|
+
if app.is_a?(Integer)
|
96
|
+
environment.applications.get(app)
|
97
|
+
else
|
98
|
+
applications = environment.applications.all(name: app)
|
99
|
+
if applications.count == 1
|
100
|
+
applications.first
|
101
|
+
else
|
102
|
+
error_msg = [
|
103
|
+
"Found multiple applications that matched that search.",
|
104
|
+
"Please be more specific by specifying the account, environment, and application name.",
|
105
|
+
"Matching applications: #{applications.map(&:name)}.",
|
106
|
+
]
|
107
|
+
if candidate_apps
|
108
|
+
error_msg << "applications on this environment: #{candidate_apps}"
|
109
|
+
end
|
110
|
+
raise Ey::Core::Cli::AmbiguousSearch.new(error_msg.join(" "))
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def unauthenticated_core_client
|
116
|
+
@unauthenticated_core_client ||= Ey::Core::Client.new(token: nil, url: core_url)
|
117
|
+
end
|
118
|
+
|
119
|
+
def core_client
|
120
|
+
@core_client ||= begin
|
121
|
+
opts = {url: core_url, config_file: self.class.core_file}
|
122
|
+
if ENV["DEBUG"]
|
123
|
+
opts[:logger] = ::Logger.new(STDOUT)
|
124
|
+
end
|
125
|
+
Ey::Core::Client.new(opts)
|
126
|
+
end
|
127
|
+
rescue RuntimeError => e
|
128
|
+
if legacy_token = e.message.match(/missing token/i) && eyrc_yaml["api_token"]
|
129
|
+
puts "Found legacy .eyrc token. Migrating to core file".green
|
130
|
+
write_core_yaml(legacy_token)
|
131
|
+
retry
|
132
|
+
elsif e.message.match(/missing token/i)
|
133
|
+
abort "Missing credentials: Run 'ey login' to retrieve your Engine Yard Cloud API token.".yellow
|
134
|
+
else
|
135
|
+
raise e
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def core_account
|
140
|
+
@_core_account ||= begin
|
141
|
+
if options[:account]
|
142
|
+
found = core_client.accounts.get(options[:account]) ||
|
143
|
+
core_client.users.current.accounts.first(name: options[:account])
|
144
|
+
if ENV["STAFF"]
|
145
|
+
found ||= core_client.accounts.first(name: options[:account])
|
146
|
+
end
|
147
|
+
unless found
|
148
|
+
account_not_found_error_message = "Couldn't find account '#{options[:account]}'"
|
149
|
+
if core_client.users.current.staff && !ENV["STAFF"]
|
150
|
+
account_not_found_error_message += " (set environment variable STAFF=1 to search all accounts)"
|
151
|
+
end
|
152
|
+
raise account_not_found_error_message
|
153
|
+
end
|
154
|
+
found
|
155
|
+
else
|
156
|
+
if core_accounts.size == 1
|
157
|
+
core_accounts.first
|
158
|
+
else
|
159
|
+
raise "Please specify --account (options: #{core_accounts.map(&:name).join(', ')})"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def core_accounts
|
166
|
+
@_core_accounts ||= begin
|
167
|
+
if ENV["STAFF"]
|
168
|
+
core_client.accounts
|
169
|
+
else
|
170
|
+
core_client.users.current.accounts
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def write_core_yaml(token=nil)
|
176
|
+
core_yaml[core_url] = token if token
|
177
|
+
File.open(self.class.core_file, "w") {|file|
|
178
|
+
file.puts core_yaml.to_yaml
|
179
|
+
}
|
180
|
+
end
|
181
|
+
|
182
|
+
def eyrc_yaml
|
183
|
+
@eyrc_yaml ||= YAML.load_file(self.class.eyrc) || {}
|
184
|
+
rescue Errno::ENOENT # we don't really care if this doesn't exist
|
185
|
+
{}
|
186
|
+
end
|
187
|
+
|
188
|
+
def self.included(base)
|
189
|
+
base.extend(ClassMethods)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'colorize'
|
2
|
+
|
3
|
+
module Ey
|
4
|
+
module Core
|
5
|
+
module Cli
|
6
|
+
module Helpers
|
7
|
+
module Deprecated
|
8
|
+
# Class-level helpers pulled into an including class
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
# A helper for deprecating a Belafonte-based command
|
12
|
+
#
|
13
|
+
# @param deprecated_title [String] the title of the command
|
14
|
+
#
|
15
|
+
# @return [NilClass] this method is not expected to return a value
|
16
|
+
def deprecate(deprecated_title)
|
17
|
+
title deprecated_title
|
18
|
+
summary 'This command has been deprecated'
|
19
|
+
description <<-DESCRIPTION
|
20
|
+
The #{meta[:title]} command has been deprecated. We apologize for any inconvenience.
|
21
|
+
DESCRIPTION
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Module magic
|
26
|
+
# @api private
|
27
|
+
def self.included(base)
|
28
|
+
base.extend(ClassMethods)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Terminate the deprecated command with a message
|
32
|
+
def handle
|
33
|
+
abort "This command is deprecated".red
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Ey
|
2
|
+
module Core
|
3
|
+
module Cli
|
4
|
+
module Helpers
|
5
|
+
module LogStreaming
|
6
|
+
|
7
|
+
def stream_deploy_log(request)
|
8
|
+
if request.finished_at
|
9
|
+
return finished_request(request)
|
10
|
+
end
|
11
|
+
unless request.read_channel
|
12
|
+
puts "Unable to stream log (streaming not enabled for this deploy)".yellow
|
13
|
+
return
|
14
|
+
end
|
15
|
+
request.subscribe { |m| print m["message"] if m.is_a?(Hash) }
|
16
|
+
puts "" # fix console output from stream
|
17
|
+
finished_request(request)
|
18
|
+
end
|
19
|
+
|
20
|
+
def finished_request(request)
|
21
|
+
if request.successful
|
22
|
+
if request.resource.successful
|
23
|
+
puts "Deploy successful!".green
|
24
|
+
else
|
25
|
+
puts "Deploy failed!".red
|
26
|
+
end
|
27
|
+
else
|
28
|
+
abort <<-EOF
|
29
|
+
Deploy failed!
|
30
|
+
Request output:
|
31
|
+
#{request.message}
|
32
|
+
EOF
|
33
|
+
.red
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|