ey-core 3.1.2 → 3.1.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.
- 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
|