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.
Files changed (63) hide show
  1. data/.ruby-version +1 -1
  2. data/.travis.yml +1 -0
  3. data/Gemfile +0 -2
  4. data/examples/add_instance.rb +74 -0
  5. data/examples/boot_env.rb +60 -0
  6. data/examples/stop_env.rb +51 -0
  7. data/examples/terminate_instance.rb +58 -0
  8. data/lib/ey-core/cli/accounts.rb +14 -6
  9. data/lib/ey-core/cli/applications.rb +32 -12
  10. data/lib/ey-core/cli/console.rb +24 -10
  11. data/lib/ey-core/cli/current_user.rb +13 -5
  12. data/lib/ey-core/cli/deploy.rb +110 -52
  13. data/lib/ey-core/cli/environments.rb +34 -12
  14. data/lib/ey-core/cli/errors.rb +10 -6
  15. data/lib/ey-core/cli/help.rb +30 -0
  16. data/lib/ey-core/cli/helpers/archive.rb +70 -0
  17. data/lib/ey-core/cli/helpers/chef.rb +35 -0
  18. data/lib/ey-core/cli/helpers/core.rb +195 -0
  19. data/lib/ey-core/cli/helpers/deprecated.rb +39 -0
  20. data/lib/ey-core/cli/helpers/log_streaming.rb +41 -0
  21. data/lib/ey-core/cli/helpers/stream_printer.rb +42 -0
  22. data/lib/ey-core/cli/init.rb +11 -8
  23. data/lib/ey-core/cli/login.rb +33 -21
  24. data/lib/ey-core/cli/logout.rb +18 -10
  25. data/lib/ey-core/cli/logs.rb +57 -35
  26. data/lib/ey-core/cli/main.rb +52 -15
  27. data/lib/ey-core/cli/recipes.rb +5 -87
  28. data/lib/ey-core/cli/recipes/apply.rb +83 -43
  29. data/lib/ey-core/cli/recipes/download.rb +48 -22
  30. data/lib/ey-core/cli/recipes/main.rb +21 -0
  31. data/lib/ey-core/cli/recipes/upload.rb +56 -23
  32. data/lib/ey-core/cli/scp.rb +11 -8
  33. data/lib/ey-core/cli/servers.rb +37 -15
  34. data/lib/ey-core/cli/ssh.rb +127 -70
  35. data/lib/ey-core/cli/status.rb +54 -14
  36. data/lib/ey-core/cli/subcommand.rb +47 -108
  37. data/lib/ey-core/cli/timeout_deploy.rb +56 -26
  38. data/lib/ey-core/cli/version.rb +13 -5
  39. data/lib/ey-core/cli/web.rb +7 -7
  40. data/lib/ey-core/cli/web/disable.rb +46 -20
  41. data/lib/ey-core/cli/web/enable.rb +40 -17
  42. data/lib/ey-core/cli/web/main.rb +21 -0
  43. data/lib/ey-core/cli/web/restart.rb +34 -15
  44. data/lib/ey-core/cli/whoami.rb +11 -3
  45. data/lib/ey-core/mock/searching.rb +4 -0
  46. data/lib/ey-core/model.rb +5 -0
  47. data/lib/ey-core/models/deployment.rb +7 -0
  48. data/lib/ey-core/models/environment.rb +5 -0
  49. data/lib/ey-core/models/request.rb +2 -0
  50. data/lib/ey-core/models/user.rb +2 -0
  51. data/lib/ey-core/requests/get_servers.rb +1 -1
  52. data/lib/ey-core/response.rb +4 -0
  53. data/lib/ey-core/subscribable.rb +3 -3
  54. data/lib/ey-core/version.rb +1 -1
  55. data/spec/ey-core/cli/accounts_spec.rb +20 -0
  56. data/spec/ey-core/cli/recipes/apply_spec.rb +4 -17
  57. data/spec/ey-core/cli/recipes/download_spec.rb +93 -0
  58. data/spec/ey-core/cli/recipes/upload_spec.rb +80 -0
  59. data/spec/servers_spec.rb +15 -0
  60. data/spec/spec_helper.rb +7 -0
  61. data/spec/support/cli_helpers.rb +38 -2
  62. metadata +116 -53
  63. checksums.yaml +0 -7
@@ -1,17 +1,39 @@
1
- class Ey::Core::Cli::Environments < Ey::Core::Cli::Subcommand
2
- title "environments"
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
- def handle
7
- environments = if option(:account)
8
- core_account_for(options).environments.all
9
- else
10
- current_accounts.map(&:environments).flatten.sort_by(&:id)
11
- end
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
- table_data = TablePrint::Printer.new(environments, [{id: {width: 10}}, :name])
15
- puts table_data.table_print
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
@@ -1,7 +1,11 @@
1
- class Ey::Core::Cli::Errors
2
- ::Ey::Core::Cli::RecipesNotFound = Class.new(ArgumentError)
3
- ::Ey::Core::Cli::NoCommand = Class.new(ArgumentError)
4
- ::Ey::Core::Cli::NoRepository = Class.new(ArgumentError)
5
- ::Ey::Core::Cli::RecipesExist = Class.new(ArgumentError)
6
- ::Ey::Core::Cli::AmbiguousSearch = Class.new(ArgumentError)
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