pbox 1.17.2

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.
Files changed (139) hide show
  1. checksums.yaml +7 -0
  2. data/COPYRIGHT +1 -0
  3. data/LICENSE +11 -0
  4. data/README.md +40 -0
  5. data/Rakefile +6 -0
  6. data/autocomplete/pbox_bash +1639 -0
  7. data/bin/pbox +37 -0
  8. data/conf/protonbox.conf +8 -0
  9. data/features/assets/deploy.tar.gz +0 -0
  10. data/features/core_feature.rb +178 -0
  11. data/features/deployments_feature.rb +127 -0
  12. data/features/domains_feature.rb +49 -0
  13. data/features/keys_feature.rb +37 -0
  14. data/features/members_feature.rb +166 -0
  15. data/lib/rhc/auth/basic.rb +64 -0
  16. data/lib/rhc/auth/token.rb +102 -0
  17. data/lib/rhc/auth/token_store.rb +53 -0
  18. data/lib/rhc/auth.rb +5 -0
  19. data/lib/rhc/autocomplete.rb +66 -0
  20. data/lib/rhc/autocomplete_templates/bash.erb +39 -0
  21. data/lib/rhc/cartridge_helpers.rb +118 -0
  22. data/lib/rhc/cli.rb +40 -0
  23. data/lib/rhc/command_runner.rb +186 -0
  24. data/lib/rhc/commands/account.rb +25 -0
  25. data/lib/rhc/commands/alias.rb +124 -0
  26. data/lib/rhc/commands/app.rb +701 -0
  27. data/lib/rhc/commands/apps.rb +20 -0
  28. data/lib/rhc/commands/authorization.rb +96 -0
  29. data/lib/rhc/commands/base.rb +174 -0
  30. data/lib/rhc/commands/cartridge.rb +326 -0
  31. data/lib/rhc/commands/deployment.rb +82 -0
  32. data/lib/rhc/commands/domain.rb +167 -0
  33. data/lib/rhc/commands/env.rb +142 -0
  34. data/lib/rhc/commands/git_clone.rb +29 -0
  35. data/lib/rhc/commands/logout.rb +51 -0
  36. data/lib/rhc/commands/member.rb +148 -0
  37. data/lib/rhc/commands/port_forward.rb +197 -0
  38. data/lib/rhc/commands/server.rb +40 -0
  39. data/lib/rhc/commands/setup.rb +60 -0
  40. data/lib/rhc/commands/snapshot.rb +137 -0
  41. data/lib/rhc/commands/ssh.rb +51 -0
  42. data/lib/rhc/commands/sshkey.rb +97 -0
  43. data/lib/rhc/commands/tail.rb +47 -0
  44. data/lib/rhc/commands/threaddump.rb +14 -0
  45. data/lib/rhc/commands.rb +396 -0
  46. data/lib/rhc/config.rb +320 -0
  47. data/lib/rhc/context_helper.rb +121 -0
  48. data/lib/rhc/core_ext.rb +202 -0
  49. data/lib/rhc/coverage_helper.rb +33 -0
  50. data/lib/rhc/deployment_helpers.rb +88 -0
  51. data/lib/rhc/exceptions.rb +232 -0
  52. data/lib/rhc/git_helpers.rb +91 -0
  53. data/lib/rhc/help_formatter.rb +55 -0
  54. data/lib/rhc/helpers.rb +477 -0
  55. data/lib/rhc/highline_extensions.rb +479 -0
  56. data/lib/rhc/json.rb +51 -0
  57. data/lib/rhc/output_helpers.rb +260 -0
  58. data/lib/rhc/rest/activation.rb +11 -0
  59. data/lib/rhc/rest/alias.rb +42 -0
  60. data/lib/rhc/rest/api.rb +87 -0
  61. data/lib/rhc/rest/application.rb +332 -0
  62. data/lib/rhc/rest/attributes.rb +36 -0
  63. data/lib/rhc/rest/authorization.rb +8 -0
  64. data/lib/rhc/rest/base.rb +79 -0
  65. data/lib/rhc/rest/cartridge.rb +154 -0
  66. data/lib/rhc/rest/client.rb +650 -0
  67. data/lib/rhc/rest/deployment.rb +18 -0
  68. data/lib/rhc/rest/domain.rb +98 -0
  69. data/lib/rhc/rest/environment_variable.rb +15 -0
  70. data/lib/rhc/rest/gear_group.rb +16 -0
  71. data/lib/rhc/rest/httpclient.rb +145 -0
  72. data/lib/rhc/rest/key.rb +44 -0
  73. data/lib/rhc/rest/membership.rb +105 -0
  74. data/lib/rhc/rest/mock.rb +1024 -0
  75. data/lib/rhc/rest/user.rb +32 -0
  76. data/lib/rhc/rest.rb +148 -0
  77. data/lib/rhc/ssh_helpers.rb +378 -0
  78. data/lib/rhc/tar_gz.rb +51 -0
  79. data/lib/rhc/usage_templates/command_help.erb +51 -0
  80. data/lib/rhc/usage_templates/command_syntax_help.erb +11 -0
  81. data/lib/rhc/usage_templates/help.erb +35 -0
  82. data/lib/rhc/usage_templates/missing_help.erb +1 -0
  83. data/lib/rhc/usage_templates/options_help.erb +12 -0
  84. data/lib/rhc/vendor/okjson.rb +600 -0
  85. data/lib/rhc/vendor/parseconfig.rb +178 -0
  86. data/lib/rhc/vendor/sshkey.rb +253 -0
  87. data/lib/rhc/vendor/zliby.rb +628 -0
  88. data/lib/rhc/version.rb +5 -0
  89. data/lib/rhc/wizard.rb +633 -0
  90. data/lib/rhc.rb +34 -0
  91. data/spec/coverage_helper.rb +89 -0
  92. data/spec/direct_execution_helper.rb +338 -0
  93. data/spec/keys/example.pem +23 -0
  94. data/spec/keys/example_private.pem +27 -0
  95. data/spec/keys/server.pem +19 -0
  96. data/spec/rest_spec_helper.rb +31 -0
  97. data/spec/rhc/assets/cert.crt +22 -0
  98. data/spec/rhc/assets/cert_key_rsa +27 -0
  99. data/spec/rhc/assets/empty.txt +0 -0
  100. data/spec/rhc/assets/env_vars.txt +7 -0
  101. data/spec/rhc/assets/env_vars_2.txt +1 -0
  102. data/spec/rhc/assets/foo.txt +1 -0
  103. data/spec/rhc/assets/targz_corrupted.tar.gz +1 -0
  104. data/spec/rhc/assets/targz_sample.tar.gz +0 -0
  105. data/spec/rhc/auth_spec.rb +442 -0
  106. data/spec/rhc/cli_spec.rb +188 -0
  107. data/spec/rhc/command_spec.rb +435 -0
  108. data/spec/rhc/commands/account_spec.rb +42 -0
  109. data/spec/rhc/commands/alias_spec.rb +333 -0
  110. data/spec/rhc/commands/app_spec.rb +754 -0
  111. data/spec/rhc/commands/apps_spec.rb +39 -0
  112. data/spec/rhc/commands/authorization_spec.rb +145 -0
  113. data/spec/rhc/commands/cartridge_spec.rb +641 -0
  114. data/spec/rhc/commands/deployment_spec.rb +286 -0
  115. data/spec/rhc/commands/domain_spec.rb +383 -0
  116. data/spec/rhc/commands/env_spec.rb +493 -0
  117. data/spec/rhc/commands/git_clone_spec.rb +80 -0
  118. data/spec/rhc/commands/logout_spec.rb +86 -0
  119. data/spec/rhc/commands/member_spec.rb +228 -0
  120. data/spec/rhc/commands/port_forward_spec.rb +217 -0
  121. data/spec/rhc/commands/server_spec.rb +69 -0
  122. data/spec/rhc/commands/setup_spec.rb +118 -0
  123. data/spec/rhc/commands/snapshot_spec.rb +179 -0
  124. data/spec/rhc/commands/ssh_spec.rb +163 -0
  125. data/spec/rhc/commands/sshkey_spec.rb +188 -0
  126. data/spec/rhc/commands/tail_spec.rb +81 -0
  127. data/spec/rhc/commands/threaddump_spec.rb +84 -0
  128. data/spec/rhc/config_spec.rb +407 -0
  129. data/spec/rhc/helpers_spec.rb +524 -0
  130. data/spec/rhc/highline_extensions_spec.rb +314 -0
  131. data/spec/rhc/json_spec.rb +30 -0
  132. data/spec/rhc/rest_application_spec.rb +248 -0
  133. data/spec/rhc/rest_client_spec.rb +752 -0
  134. data/spec/rhc/rest_spec.rb +740 -0
  135. data/spec/rhc/targz_spec.rb +55 -0
  136. data/spec/rhc/wizard_spec.rb +756 -0
  137. data/spec/spec_helper.rb +575 -0
  138. data/spec/wizard_spec_helper.rb +330 -0
  139. metadata +435 -0
@@ -0,0 +1,91 @@
1
+ require 'open4'
2
+ require 'fileutils'
3
+
4
+ module RHC
5
+ module GitHelpers
6
+ def git_version
7
+ @git_version ||= `git --version 2>&1`.strip #:nocov:
8
+ end
9
+
10
+ def has_git?
11
+ @has_git ||= begin
12
+ @git_version = nil
13
+ git_version
14
+ $?.success?
15
+ rescue
16
+ false
17
+ end
18
+ end
19
+
20
+ def git_clone_deploy_hooks(repo_dir)
21
+ debug "Deploy default hooks"
22
+ Dir.chdir(repo_dir) do |dir|
23
+ Dir.glob(".protonbox/git_hooks/*") do |hook|
24
+ FileUtils.cp(hook, ".git/hooks/")
25
+ end
26
+ end
27
+ end
28
+
29
+ def git_clone_application(app)
30
+ repo_dir = options.repo || app.name
31
+
32
+ debug "Pulling new repo down"
33
+ dir = git_clone_repo(app.git_url, repo_dir)
34
+
35
+ debug "Configuring git repo"
36
+ Dir.chdir(repo_dir) do
37
+ git_config_set "pbox.app-id", app.id
38
+ git_config_set "pbox.app-name", app.name
39
+ git_config_set "pbox.domain-name", app.domain_id
40
+ end
41
+
42
+ git_clone_deploy_hooks(repo_dir)
43
+
44
+ dir
45
+ end
46
+
47
+ # :nocov: These all call external binaries so test them in cucumber
48
+ def git_config_get(key)
49
+ config_get_cmd = "git config --get #{key}"
50
+ value = %x[#{config_get_cmd}].strip
51
+ debug "Git config '#{config_get_cmd}' returned '#{value}'"
52
+ value = nil if $?.exitstatus != 0 or value.empty?
53
+
54
+ value
55
+ end
56
+
57
+ def git_config_set(key, value)
58
+ unset_cmd = "git config --unset-all #{key}"
59
+ config_cmd = "git config --add #{key} #{value}"
60
+ debug "Adding #{key} = #{value} to git config"
61
+ commands = [unset_cmd, config_cmd]
62
+ commands.each do |cmd|
63
+ debug "Running #{cmd} 2>&1"
64
+ output = %x[#{cmd} 2>&1]
65
+ raise RHC::GitException, "Error while adding config values to git - #{output}" unless output.empty?
66
+ end
67
+ end
68
+ # :nocov:
69
+
70
+ def git_clone_repo(git_url, repo_dir)
71
+ # quote the repo to avoid input injection risk
72
+ destination = (repo_dir ? " \"#{repo_dir}\"" : "")
73
+ cmd = "git clone #{git_url}#{destination}"
74
+ debug "Running #{cmd}"
75
+
76
+ status, stdout, stderr = run_with_tee(cmd)
77
+
78
+ if status != 0
79
+ case stderr
80
+ when /fatal: destination path '[^']*' already exists and is not an empty directory./
81
+ raise RHC::GitDirectoryExists, "The directory you are cloning into already exists."
82
+ when /^Permission denied \(.*?publickey.*?\).$/
83
+ raise RHC::GitPermissionDenied, "You don't have permission to access this repository. Check that your SSH public keys are correct."
84
+ else
85
+ raise RHC::GitException, "Unable to clone your repository. Called Git with: #{cmd}"
86
+ end
87
+ end
88
+ File.expand_path(repo_dir)
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,55 @@
1
+ require 'commander/help_formatters/base'
2
+
3
+ module RHC
4
+ class HelpFormatter < Commander::HelpFormatter::Terminal
5
+ def template(name)
6
+ ERB.new(File.read(File.join(File.dirname(__FILE__), 'usage_templates', "#{name}.erb")), nil, '-')
7
+ end
8
+ def render
9
+ template(:help).result RunnerHelpBindings.new(@runner).get_binding
10
+ end
11
+ def render_command_syntax command
12
+ template(:command_syntax_help).result command.get_binding
13
+ end
14
+ def render_options runner
15
+ template(:options_help).result RunnerHelpBindings.new(runner).get_binding
16
+ end
17
+ end
18
+
19
+ class RunnerHelpBindings < SimpleDelegator
20
+ include RHC::Helpers
21
+
22
+ def commands
23
+ __getobj__.instance_variable_get(:@commands)
24
+ end
25
+
26
+ def get_binding
27
+ binding
28
+ end
29
+ end
30
+
31
+ class CommandHelpBindings
32
+ include RHC::Helpers
33
+
34
+ def initialize(command, instance_commands, runner)
35
+ @command = command
36
+ @actions =
37
+ if command.root?
38
+ instance_commands.sort_by{ |c| c[0] }.collect do |command_name, command_class|
39
+ next if command_class.summary.nil?
40
+ m = /^#{command.name}[\-]([^ ]+)/.match(command_name)
41
+ # if we have a match and it is not an alias then we can use it
42
+ m and command_name == command_class.name ? {:name => m[1], :summary => command_class.summary || ""} : nil
43
+ end
44
+ else
45
+ []
46
+ end
47
+ @actions.compact!
48
+ @global_options = runner.options
49
+ @runner = runner
50
+ end
51
+ def program(*args)
52
+ @runner.program *args
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,477 @@
1
+ require 'commander/user_interaction'
2
+ require 'rhc/version'
3
+ require 'rhc/config'
4
+ require 'rhc/output_helpers'
5
+ require 'rbconfig'
6
+
7
+ require 'resolv'
8
+
9
+ OptionParser.accept(URI) {|s,| URI.parse(s) if s}
10
+
11
+ module RHC
12
+
13
+ module Helpers
14
+ private
15
+ def self.global_option(*args, &block)
16
+ RHC::Commands.global_option *args, &block
17
+ end
18
+ end
19
+
20
+ module Helpers
21
+
22
+ # helpers always have Commander UI available
23
+ include Commander::UI
24
+ include Commander::UI::AskForClass
25
+ include RHC::OutputHelpers
26
+
27
+ extend self
28
+
29
+ def decode_json(s)
30
+ RHC::Vendor::OkJson.decode(s)
31
+ end
32
+
33
+ def system_path(path)
34
+ return path.gsub(File::SEPARATOR, File::ALT_SEPARATOR) if File.const_defined?('ALT_SEPARATOR') and File::ALT_SEPARATOR.present?
35
+ path
36
+ end
37
+
38
+ PREFIX = %W(TB GB MB KB B).freeze
39
+
40
+ def human_size( s )
41
+ return "unknown" unless s
42
+ s = s.to_f
43
+ i = PREFIX.length - 1
44
+ while s > 500 && i > 0
45
+ i -= 1
46
+ s /= 1000
47
+ end
48
+ ((s > 9 || s.modulo(1) < 0.1 ? '%d' : '%.1f') % s) + ' ' + PREFIX[i]
49
+ end
50
+
51
+ def date(s)
52
+ return nil unless s.present?
53
+ now = Date.today
54
+ d = datetime_rfc3339(s).to_time
55
+ if now.year == d.year
56
+ return d.strftime('%l:%M %p').strip if now.yday == d.yday
57
+ d.strftime('%b %d %l:%M %p')
58
+ else
59
+ d.strftime('%b %d, %Y %l:%M %p')
60
+ end
61
+ rescue ArgumentError
62
+ "Unknown date"
63
+ end
64
+
65
+ def distance_of_time_in_words(from_time, to_time = 0)
66
+ from_time = from_time.to_time if from_time.respond_to?(:to_time)
67
+ to_time = to_time.to_time if to_time.respond_to?(:to_time)
68
+ distance_in_minutes = (((to_time - from_time).abs)/60).round
69
+ distance_in_seconds = ((to_time - from_time).abs).round
70
+
71
+ case distance_in_minutes
72
+ when 0..1
73
+ return distance_in_minutes == 0 ?
74
+ "less than 1 minute" :
75
+ "#{distance_in_minutes} minute"
76
+
77
+ when 2..44 then "#{distance_in_minutes} minutes"
78
+ when 45..89 then "about 1 hour"
79
+ when 90..1439 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
80
+ when 1440..2519 then "about 1 day"
81
+ when 2520..43199 then "#{(distance_in_minutes.to_f / 1440.0).round} days"
82
+ when 43200..86399 then "about 1 month"
83
+ else
84
+ "about #{(distance_in_minutes.to_f / 43200.0).round} months"
85
+ end
86
+ end
87
+
88
+ def datetime_rfc3339(s)
89
+ DateTime.strptime(s, '%Y-%m-%dT%H:%M:%S%z')
90
+ # Replace with d = DateTime.rfc3339(s)
91
+ end
92
+
93
+ #
94
+ # Web related requests
95
+ #
96
+
97
+ def user_agent
98
+ "pbox/#{RHC::VERSION::STRING} (ruby #{RUBY_VERSION}; #{RUBY_PLATFORM})#{" (API #{RHC::Rest::API_VERSION})" rescue ''}"
99
+ end
100
+
101
+ #
102
+ # Global config
103
+ #
104
+
105
+ global_option '-l', '--pblogin LOGIN', "ProtonBox login"
106
+ global_option '-p', '--password PASSWORD', "ProtonBox password"
107
+ global_option '--token TOKEN', "An authorization token for accessing your account."
108
+
109
+ global_option '-d', '--debug', "Turn on debugging", :hide => true
110
+
111
+ global_option '--server NAME', String, 'A ProtonBox server hostname (default: api.protonbox.com)'
112
+ global_option '-k', '--insecure', "Allow insecure SSL connections. Potential security risk.", :hide => true
113
+
114
+ global_option '--limit INTEGER', Integer, "Maximum number of simultaneous operations to execute.", :hide => true
115
+ global_option '--raw', "Do not format the output from the requested operations.", :hide => true
116
+ global_option '--always-prefix', "Include the gear prefix on all output from the server.", :hide => true
117
+
118
+ OptionParser.accept(SSLVersion = Class.new){ |s| OpenSSL::SSL::SSLContext::METHODS.find{ |m| m.to_s.downcase == s.downcase } or raise OptionParser::InvalidOption.new(nil, "The provided SSL version '#{s}' is not valid. Supported values: #{OpenSSL::SSL::SSLContext::METHODS.map(&:to_s).map(&:downcase).join(', ')}") }
119
+ global_option '--ssl-version VERSION', SSLVersion, "The version of SSL to use", :hide => true do |value|
120
+ raise RHC::Exception, "You are using an older version of the httpclient gem which prevents the use of --ssl-version. Please run 'gem update httpclient' to install a newer version (2.2.6 or newer)." unless HTTPClient::SSLConfig.method_defined? :ssl_version
121
+ end
122
+ global_option '--ssl-ca-file FILE', "An SSL certificate CA file (may contain multiple certs)", :hide => true do |value|
123
+ debug certificate_file(value)
124
+ end
125
+ global_option '--ssl-client-cert-file FILE', "An SSL x509 client certificate file", :hide => true do |value|
126
+ debug certificate_file(value)
127
+ end
128
+
129
+ global_option('--timeout SECONDS', Integer, 'The timeout for operations') do |value|
130
+ raise RHC::Exception, "Timeout must be a positive integer" unless value > 0
131
+ end
132
+ global_option '--noprompt', "Suppress all interactive operations command", :hide => true do
133
+ $terminal.page_at = nil
134
+ end
135
+ global_option '--config FILE', "Path of a different config file (default: #{system_path("~/.protonbox/protonbox.conf")})", :hide => true
136
+ global_option '--clean', "Ignore any saved configuration options", :hide => true
137
+ global_option '--mock', "Run in mock mode", :hide => true do
138
+ #:nocov:
139
+ require 'rhc/rest/mock'
140
+ RHC::Rest::Mock.start
141
+ #:nocov:
142
+ end
143
+
144
+ ROLES = {'view' => 'viewer', 'edit' => 'editor', 'admin' => 'administrator'}
145
+ OptionParser.accept(Role = Class.new) do |s|
146
+ s.downcase!
147
+ (ROLES.has_key?(s) && s) or
148
+ raise OptionParser::InvalidOption.new(nil, "The provided role '#{s}' is not valid. Supported values: #{ROLES.keys.join(', ')}")
149
+ end
150
+
151
+ def role_name(s)
152
+ ROLES[s.downcase]
153
+ end
154
+
155
+ def protonbox_server
156
+ to_host((options.server rescue nil) || ENV['PROTONBOX_SERVER'] || "api.protonbox.com")
157
+ end
158
+ def protonbox_online_server?
159
+ protonbox_server =~ /api.protonbox.com$/i
160
+ end
161
+ def protonbox_url
162
+ "https://#{protonbox_server}"
163
+ end
164
+
165
+ def to_host(s)
166
+ s =~ %r(^http(?:s)?://) ? URI(s).host : s
167
+ end
168
+ def to_uri(s)
169
+ begin
170
+ URI(s =~ %r(^http(?:s)?://) ? s : "https://#{s}")
171
+ rescue URI::InvalidURIError
172
+ raise RHC::InvalidURIException.new(s)
173
+ end
174
+ end
175
+
176
+ def ssh_string(ssh_url)
177
+ return nil if ssh_url.blank?
178
+ uri = URI.parse(ssh_url)
179
+ "#{uri.user}@#{uri.host}"
180
+ rescue => e
181
+ RHC::Helpers.debug_error(e)
182
+ ssh_url
183
+ end
184
+
185
+ def ssh_string_parts(ssh_url)
186
+ uri = URI.parse(ssh_url)
187
+ [uri.host, uri.user]
188
+ end
189
+
190
+ def protonbox_rest_endpoint
191
+ uri = to_uri((options.server rescue nil) || ENV['PROTONBOX_SERVER'] || "api.protonbox.com")
192
+ uri.path = '/broker/rest/api' if uri.path.blank? || uri.path == '/'
193
+ uri
194
+ end
195
+
196
+ def token_for_user
197
+ options.token or (token_store.get(options.pblogin, options.server) if options.pblogin && options.use_authorization_tokens)
198
+ end
199
+
200
+ def client_from_options(opts)
201
+ RHC::Rest::Client.new({
202
+ :url => protonbox_rest_endpoint.to_s,
203
+ :debug => options.debug,
204
+ :timeout => options.timeout,
205
+ :warn => BOUND_WARNING,
206
+ }.merge!(ssl_options).merge!(opts))
207
+ end
208
+
209
+ def ssl_options
210
+ {
211
+ :ssl_version => options.ssl_version,
212
+ :client_cert => certificate_file(options.ssl_client_cert),
213
+ :ca_file => options.ssl_ca_file && File.expand_path(options.ssl_ca_file),
214
+ :verify_mode => options.insecure ? OpenSSL::SSL::VERIFY_NONE : nil,
215
+ }.delete_if{ |k,v| v.nil? }
216
+ end
217
+
218
+ def certificate_file(file)
219
+ file && OpenSSL::X509::Certificate.new(IO.read(File.expand_path(file)))
220
+ rescue => e
221
+ debug e
222
+ raise OptionParser::InvalidOption.new(nil, "The certificate '#{file}' cannot be loaded: #{e.message} (#{e.class})")
223
+ end
224
+
225
+
226
+ #
227
+ # Output helpers
228
+ #
229
+
230
+ def interactive?
231
+ $stdin.tty? and $stdout.tty? and not options.noprompt
232
+ end
233
+
234
+ def debug(*args)
235
+ $terminal.debug(*args)
236
+ end
237
+ def debug_error(*args)
238
+ $terminal.debug_error(*args)
239
+ end
240
+ def debug?
241
+ $terminal.debug?
242
+ end
243
+
244
+ def exec(cmd)
245
+ output = Kernel.send(:`, cmd)
246
+ [$?.exitstatus, output]
247
+ end
248
+
249
+ def disable_deprecated?
250
+ ENV['DISABLE_DEPRECATED'] == '1'
251
+ end
252
+
253
+ def deprecated_command(correct, short=false)
254
+ deprecated("This command is deprecated. Please use '#{correct}' instead.", short)
255
+ end
256
+
257
+ def deprecated(msg,short = false)
258
+ raise DeprecatedError.new(msg % ['an error','a warning',0]) if disable_deprecated?
259
+ warn "Warning: #{msg}\n" % ['a warning','an error',1]
260
+ end
261
+
262
+ #
263
+ # By default, agree should take a single character in interactive
264
+ #
265
+ def agree(*args, &block)
266
+ #args.push(interactive?.presence) if args.length == 1
267
+ block = lambda do |q|
268
+ q.validate = /\A(?:y|yes|n|no)\Z/i
269
+ end unless block_given?
270
+ super *args, &block
271
+ end
272
+
273
+ def confirm_action(question)
274
+ return if options.confirm
275
+ return if !options.noprompt && paragraph{ agree "#{question} (yes|no): " }
276
+ raise RHC::ConfirmationError
277
+ end
278
+
279
+ def success(msg, *args)
280
+ say color(msg, :green), *args
281
+ end
282
+
283
+ def info(msg, *args)
284
+ say color(msg, :cyan), *args
285
+ end
286
+
287
+ def warn(msg, *args)
288
+ say color(msg, :yellow), *args
289
+ end
290
+
291
+ def error(msg, *args)
292
+ say color(msg, :red), *args
293
+ end
294
+
295
+ # OVERRIDE: Replaces default commander behavior
296
+ def color(item, *args)
297
+ if item.is_a? Array
298
+ item.map{ |i| $terminal.color(i, *args) }
299
+ else
300
+ $terminal.color(item, *args)
301
+ end
302
+ end
303
+
304
+ [:pager, :indent, :paragraph, :section, :header, :table, :table_args].each do |sym|
305
+ define_method(sym) do |*args, &block|
306
+ $terminal.send(sym, *args, &block)
307
+ end
308
+ end
309
+
310
+ def pluralize(count, s)
311
+ count == 1 ? "#{count} #{s}" : "#{count} #{s}s"
312
+ end
313
+
314
+ # This will format table headings for a consistent look and feel
315
+ # If a heading isn't explicitly defined, it will attempt to look up the parts
316
+ # If those aren't found, it will capitalize the string
317
+ def table_heading(value)
318
+ # Set the default proc to look up undefined values
319
+ headings = Hash.new do |hash,key|
320
+ items = key.to_s.split('_')
321
+ # Look up each piece individually
322
+ hash[key] = items.length > 1 ?
323
+ # Recusively look up the heading for the parts
324
+ items.map{|x| headings[x.to_sym]}.join(' ') :
325
+ # Capitalize if this part isn't defined
326
+ items.first.capitalize
327
+ end
328
+
329
+ # Predefined headings (or parts of headings)
330
+ headings.merge!({
331
+ :creation_time => "Created",
332
+ :expires_in_seconds => "Expires In",
333
+ :uuid => "ID",
334
+ :id => 'ID',
335
+ :current_scale => "Current",
336
+ :scales_from => "Minimum",
337
+ :scales_to => "Maximum",
338
+ :gear_sizes => "Allowed Gear Sizes",
339
+ :consumed_gears => "Gears Used",
340
+ :max_gears => "Gears Allowed",
341
+ :max_domains => "Domains Allowed",
342
+ :compact_members => "Members",
343
+ :gear_info => "Gears",
344
+ :plan_id => "Plan",
345
+ :url => "URL",
346
+ :ssh_string => "SSH",
347
+ :connection_info => "Connection URL",
348
+ :gear_profile => "Gear Size",
349
+ :visible_to_ssh? => 'Available',
350
+ :downloaded_cartridge_url => 'From',
351
+ :auto_deploy => 'Deployment',
352
+ :sha1 => 'SHA1',
353
+ :ref => 'Git Reference'
354
+ })
355
+
356
+ headings[value]
357
+ end
358
+
359
+ class StringTee < StringIO
360
+ attr_reader :tee
361
+ def initialize(other)
362
+ @tee = other
363
+ super()
364
+ end
365
+ def <<(buf)
366
+ tee << buf
367
+ super
368
+ end
369
+ end
370
+
371
+ ##
372
+ # results
373
+ #
374
+ # highline helper which creates a paragraph with a header
375
+ # to distinguish the final results of a command from other output
376
+ #
377
+ def results(&block)
378
+ section(:top => 1, :bottom => 0) do
379
+ say "RESULT:"
380
+ yield
381
+ end
382
+ end
383
+
384
+ # Platform helpers
385
+ def jruby? ; RUBY_PLATFORM =~ /java/i end
386
+ def windows? ; RUBY_PLATFORM =~ /win(32|dows|ce)|djgpp|(ms|cyg|bcc)win|mingw32/i end
387
+ def unix? ; !jruby? && !windows? end
388
+ def mac? ; RbConfig::CONFIG['host_os'] =~ /^darwin/ end
389
+
390
+ #
391
+ # Check if host exists
392
+ #
393
+ def host_exists?(host)
394
+ # :nocov:
395
+ # Patch for BZ840938 to support Ruby 1.8 on machines without /etc/resolv.conf
396
+ dns = Resolv::DNS.new((Resolv::DNS::Config.default_config_hash || {}))
397
+ dns.getresources(host, Resolv::DNS::Resource::IN::A).any?
398
+ # :nocov:
399
+ end
400
+
401
+ def hosts_file_contains?(host)
402
+ with_tolerant_encoding do
403
+ begin
404
+ resolver = Resolv::Hosts.new
405
+ resolver.getaddress host
406
+ rescue => e
407
+ debug "Error while resolving with Resolv::Hosts: #{e.message}(#{e.class})\n #{e.backtrace.join("\n ")}"
408
+ end
409
+ end
410
+ end
411
+
412
+ def with_tolerant_encoding(&block)
413
+ # :nocov:
414
+ if RUBY_VERSION.to_f >= 1.9
415
+ orig_default_internal = Encoding.default_internal
416
+ Encoding.default_internal = 'ISO-8859-1'
417
+ else
418
+ orig_default_kcode = $KCODE
419
+ $KCODE = 'N'
420
+ end
421
+ yield
422
+ ensure
423
+ if RUBY_VERSION.to_f >= 1.9
424
+ Encoding.default_internal = orig_default_internal
425
+ else
426
+ $KCODE = orig_default_kcode
427
+ end
428
+ # :nocov:
429
+ end
430
+
431
+ # Run a command and export its output to the user. Output is not capturable
432
+ # on all platforms.
433
+ def run_with_tee(cmd)
434
+ status, stdout, stderr = nil
435
+
436
+ if windows?
437
+ #:nocov: TODO: Test block
438
+ system(cmd)
439
+ status = $?.exitstatus
440
+ #:nocov:
441
+ else
442
+ stdout, stderr = [$stdout, $stderr].map{ |t| StringTee.new(t) }
443
+ status = Open4.spawn(cmd, 'stdout' => stdout, 'stderr' => stderr, 'quiet' => true)
444
+ stdout, stderr = [stdout, stderr].map(&:string)
445
+ end
446
+
447
+ [status, stdout, stderr]
448
+ end
449
+
450
+ def env_var_regex_pattern
451
+ /^([a-zA-Z_][\w]*)=(.*)$/
452
+ end
453
+
454
+ def collect_env_vars(items)
455
+ return nil if items.blank?
456
+
457
+ env_vars = []
458
+
459
+ Array(items).each do |item|
460
+ if match = item.match(env_var_regex_pattern)
461
+ name, value = match.captures
462
+ env_vars << RHC::Rest::EnvironmentVariable.new({ :name => name, :value => value })
463
+ elsif File.file? item
464
+ File.readlines(item).each do |line|
465
+ if match = line.match(env_var_regex_pattern)
466
+ name, value = match.captures
467
+ env_vars << RHC::Rest::EnvironmentVariable.new({ :name => name, :value => value })
468
+ end
469
+ end
470
+ end
471
+ end
472
+ env_vars
473
+ end
474
+
475
+ BOUND_WARNING = self.method(:warn).to_proc
476
+ end
477
+ end