pebblescape 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,147 @@
1
+ require "pebbles/command/base"
2
+
3
+ # manage app config vars
4
+ #
5
+ class Pebbles::Command::Config < Pebbles::Command::Base
6
+
7
+ # config
8
+ #
9
+ # display the config vars for an app
10
+ #
11
+ # -s, --shell # output config vars in shell format
12
+ #
13
+ #Examples:
14
+ #
15
+ # $ pebbles config
16
+ # A: one
17
+ # B: two
18
+ #
19
+ # $ pebbles config --shell
20
+ # A=one
21
+ # B=two
22
+ #
23
+ def index
24
+ validate_arguments!
25
+
26
+ vars = if options[:shell]
27
+ api.get_config_vars(app).body
28
+ else
29
+ api.request(
30
+ :expects => 200,
31
+ :method => :get,
32
+ :path => "/apps/#{app}/config_vars",
33
+ :query => { "symbolic" => true }
34
+ ).body
35
+ end
36
+
37
+ if vars.empty?
38
+ display("#{app} has no config vars.")
39
+ else
40
+ vars.each {|key, value| vars[key] = value.to_s}
41
+ if options[:shell]
42
+ vars.keys.sort.each do |key|
43
+ display(%{#{key}=#{vars[key]}})
44
+ end
45
+ else
46
+ styled_header("#{app} Config Vars")
47
+ styled_hash(vars)
48
+ end
49
+ end
50
+ end
51
+
52
+ # config:set KEY1=VALUE1 [KEY2=VALUE2 ...]
53
+ #
54
+ # set one or more config vars
55
+ #
56
+ #Example:
57
+ #
58
+ # $ pebbles config:set A=one
59
+ # Setting config vars and restarting example... done, v123
60
+ # A: one
61
+ #
62
+ # $ pebbles config:set A=one B=two
63
+ # Setting config vars and restarting example... done, v123
64
+ # A: one
65
+ # B: two
66
+ #
67
+ def set
68
+ unless args.size > 0 and args.all? { |a| a.include?('=') }
69
+ error("Usage: pebbles config:set KEY1=VALUE1 [KEY2=VALUE2 ...]\nMust specify KEY and VALUE to set.")
70
+ end
71
+
72
+ vars = args.inject({}) do |vars, arg|
73
+ key, value = arg.split('=', 2)
74
+ vars[key] = value
75
+ vars
76
+ end
77
+
78
+ action("Setting config vars and restarting #{app}") do
79
+ api.put_config_vars(app, vars)
80
+
81
+ @status = begin
82
+ if release = api.get_release(app, 'current').body
83
+ release['name']
84
+ end
85
+ rescue Pebbles::API::Errors::RequestFailed => e
86
+ end
87
+ end
88
+
89
+ vars.each {|key, value| vars[key] = value.to_s}
90
+ styled_hash(vars)
91
+ end
92
+
93
+ alias_command "config:add", "config:set"
94
+
95
+ # config:get KEY
96
+ #
97
+ # display a config value for an app
98
+ #
99
+ #Examples:
100
+ #
101
+ # $ pebbles config:get A
102
+ # one
103
+ #
104
+ def get
105
+ unless key = shift_argument
106
+ error("Usage: pebbles config:get KEY\nMust specify KEY.")
107
+ end
108
+ validate_arguments!
109
+
110
+ vars = api.get_config_vars(app).body
111
+ key, value = vars.detect {|k,v| k == key}
112
+ display(value.to_s)
113
+ end
114
+
115
+ # config:unset KEY1 [KEY2 ...]
116
+ #
117
+ # unset one or more config vars
118
+ #
119
+ # $ pebbles config:unset A
120
+ # Unsetting A and restarting example... done, v123
121
+ #
122
+ # $ pebbles config:unset A B
123
+ # Unsetting A and restarting example... done, v123
124
+ # Unsetting B and restarting example... done, v124
125
+ #
126
+ def unset
127
+ if args.empty?
128
+ error("Usage: pebbles config:unset KEY1 [KEY2 ...]\nMust specify KEY to unset.")
129
+ end
130
+
131
+ args.each do |key|
132
+ action("Unsetting #{key} and restarting #{app}") do
133
+ api.delete_config_var(app, key)
134
+
135
+ @status = begin
136
+ if release = api.get_release(app, 'current').body
137
+ release['name']
138
+ end
139
+ rescue Pebbles::API::Errors::RequestFailed => e
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ alias_command "config:remove", "config:unset"
146
+
147
+ end
@@ -0,0 +1,124 @@
1
+ require "pebbles/command/base"
2
+
3
+ # list commands and display help
4
+ #
5
+ class Pebbles::Command::Help < Pebbles::Command::Base
6
+
7
+ PRIMARY_NAMESPACES = %w( auth apps ps run addons config releases domains logs sharing )
8
+
9
+ def index
10
+ if command = args.shift
11
+ help_for_command(command)
12
+ else
13
+ help_for_root
14
+ end
15
+ end
16
+
17
+ alias_command "-h", "help"
18
+ alias_command "--help", "help"
19
+
20
+ private
21
+
22
+ def commands_for_namespace(name)
23
+ Pebbles::Command.commands.values.select do |command|
24
+ command[:namespace] == name && command[:command] != name
25
+ end
26
+ end
27
+
28
+ def namespaces
29
+ namespaces = Pebbles::Command.namespaces
30
+ namespaces.delete("app")
31
+ namespaces
32
+ end
33
+
34
+ def commands
35
+ Pebbles::Command.commands
36
+ end
37
+
38
+ def skip_namespace?(ns)
39
+ return true if ns[:description] =~ /DEPRECATED:/
40
+ return true if ns[:description] =~ /HIDDEN:/
41
+ false
42
+ end
43
+
44
+ def skip_command?(command)
45
+ return true if command[:help] =~ /DEPRECATED:/
46
+ return true if command[:help] =~ /^ HIDDEN:/
47
+ false
48
+ end
49
+
50
+ def primary_namespaces
51
+ PRIMARY_NAMESPACES.map { |name| namespaces[name] }.compact
52
+ end
53
+
54
+ def additional_namespaces
55
+ (namespaces.values - primary_namespaces)
56
+ end
57
+
58
+ def summary_for_namespaces(namespaces)
59
+ size = longest(namespaces.map { |n| n[:name] })
60
+ namespaces.sort_by {|namespace| namespace[:name]}.each do |namespace|
61
+ next if skip_namespace?(namespace)
62
+ name = namespace[:name]
63
+ namespace[:description]
64
+ puts " %-#{size}s # %s" % [ name, namespace[:description] ]
65
+ end
66
+ end
67
+
68
+ def help_for_root
69
+ puts "Usage: pebbles COMMAND [--app APP] [command-specific-options]"
70
+ puts
71
+ puts "Primary help topics, type \"pebbles help TOPIC\" for more details:"
72
+ puts
73
+ summary_for_namespaces(primary_namespaces)
74
+ puts
75
+ puts "Additional topics:"
76
+ puts
77
+ summary_for_namespaces(additional_namespaces)
78
+ puts
79
+ end
80
+
81
+ def help_for_namespace(name)
82
+ namespace_commands = commands_for_namespace(name)
83
+
84
+ unless namespace_commands.empty?
85
+ size = longest(namespace_commands.map { |c| c[:banner] })
86
+ namespace_commands.sort_by { |c| c[:banner].to_s }.each do |command|
87
+ next if skip_command?(command)
88
+ command[:summary]
89
+ puts " %-#{size}s # %s" % [ command[:banner], command[:summary] ]
90
+ end
91
+ end
92
+ end
93
+
94
+ def help_for_command(name)
95
+ if command_alias = Pebbles::Command.command_aliases[name]
96
+ display("Alias: #{name} redirects to #{command_alias}")
97
+ name = command_alias
98
+ end
99
+ if command = commands[name]
100
+ puts "Usage: pebbles #{command[:banner]}"
101
+
102
+ if command[:help].strip.length > 0
103
+ help = command[:help].split("\n").reject do |line|
104
+ line =~ /HIDDEN/
105
+ end
106
+ puts help[1..-1].join("\n")
107
+ end
108
+ puts
109
+ end
110
+
111
+ namespace_commands = commands_for_namespace(name).reject do |command|
112
+ command[:help] =~ /DEPRECATED/
113
+ end
114
+
115
+ if !namespace_commands.empty?
116
+ puts "Additional commands, type \"pebbles help COMMAND\" for more details:"
117
+ puts
118
+ help_for_namespace(name)
119
+ puts
120
+ elsif command.nil?
121
+ error "#{name} is not a pebbles command. See `pebbles help`."
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,69 @@
1
+ require "pebbles/helpers"
2
+
3
+ module Pebbles::Git
4
+ extend Pebbles::Helpers
5
+
6
+ def self.check_git_version
7
+ return unless running_on_windows? || running_on_a_mac?
8
+ if git_is_insecure(git_version)
9
+ warn_about_insecure_git
10
+ end
11
+ end
12
+
13
+ def self.git_is_insecure(version)
14
+ v = Version.parse(version)
15
+ if v < Version.parse('1.8.5.6')
16
+ return true
17
+ end
18
+ if v >= Version.parse('1.9') && v < Version.parse('1.9.5')
19
+ return true
20
+ end
21
+ if v >= Version.parse('2.0') && v < Version.parse('2.0.5')
22
+ return true
23
+ end
24
+ if v >= Version.parse('2.1') && v < Version.parse('2.1.4')
25
+ return true
26
+ end
27
+ if v >= Version.parse('2.2') && v < Version.parse('2.2.1')
28
+ return true
29
+ end
30
+ return false
31
+ end
32
+
33
+ def self.warn_about_insecure_git
34
+ warn "Your version of git is #{git_version}. Which has serious security vulnerabilities."
35
+ warn "More information here: https://blog.heroku.com/archives/2014/12/23/update_your_git_clients_on_windows_and_os_x"
36
+ end
37
+
38
+ private
39
+
40
+ def self.git_version
41
+ version = /git version ([\d\.]+)/.match(`git --version`)
42
+ error("Git appears to be installed incorrectly\nEnsure that `git --version` outputs the version correctly.") unless version
43
+ version[1]
44
+ rescue Errno::ENOENT
45
+ error("Git must be installed to use pebbles.\nSee instructions here: http://git-scm.com")
46
+ end
47
+
48
+ class Version
49
+ include Comparable
50
+
51
+ attr_accessor :major, :minor, :patch, :special
52
+
53
+ def initialize(major, minor=0, patch=0, special=0)
54
+ @major, @minor, @patch, @special = major, minor, patch, special
55
+ end
56
+
57
+ def self.parse(s)
58
+ digits = s.split('.').map { |i| i.to_i }
59
+ Version.new(*digits)
60
+ end
61
+
62
+ def <=>(other)
63
+ return major <=> other.major unless (major <=> other.major) == 0
64
+ return minor <=> other.minor unless (minor <=> other.minor) == 0
65
+ return patch <=> other.patch unless (patch <=> other.patch) == 0
66
+ return special <=> other.special
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,284 @@
1
+ module Pebbles
2
+ module Helpers
3
+ extend self
4
+
5
+ def home_directory
6
+ return Dir.home if defined? Dir.home # Ruby 1.9+
7
+ running_on_windows? ? ENV['USERPROFILE'].gsub("\\","/") : ENV['HOME']
8
+ end
9
+
10
+ def running_on_windows?
11
+ RUBY_PLATFORM =~ /mswin32|mingw32/
12
+ end
13
+
14
+ def running_on_a_mac?
15
+ RUBY_PLATFORM =~ /-darwin\d/
16
+ end
17
+
18
+ def launchy(message, url)
19
+ action(message) do
20
+ require("launchy")
21
+ launchy = Launchy.open(url)
22
+ if launchy.respond_to?(:join)
23
+ launchy.join
24
+ end
25
+ end
26
+ end
27
+
28
+ def output_with_bang(message="", new_line=true)
29
+ return if message.to_s.strip == ""
30
+ display(format_with_bang(message), new_line)
31
+ end
32
+
33
+ def format_with_bang(message)
34
+ return '' if message.to_s.strip == ""
35
+ " ! " + message.split("\n").join("\n ! ")
36
+ end
37
+
38
+ def display(msg="", new_line=true)
39
+ if new_line
40
+ puts(msg)
41
+ else
42
+ print(msg)
43
+ end
44
+ $stdout.flush
45
+ end
46
+
47
+ def debug(*args)
48
+ $stderr.puts(*args) if debugging?
49
+ end
50
+
51
+ def debugging?
52
+ ENV['PEBBLES_DEBUG']
53
+ end
54
+
55
+ def ask
56
+ $stdin.gets.to_s.strip
57
+ end
58
+
59
+ def error(message, report=false)
60
+ if Pebbles::Helpers.error_with_failure
61
+ display("failed")
62
+ Pebbles::Helpers.error_with_failure = false
63
+ end
64
+ $stderr.puts(format_with_bang(message))
65
+ exit(1)
66
+ end
67
+
68
+ def self.error_with_failure
69
+ @@error_with_failure ||= false
70
+ end
71
+
72
+ def self.error_with_failure=(new_error_with_failure)
73
+ @@error_with_failure = new_error_with_failure
74
+ end
75
+
76
+ def hputs(string='')
77
+ Kernel.puts(string)
78
+ end
79
+
80
+ def longest(items)
81
+ items.map { |i| i.to_s.length }.sort.last
82
+ end
83
+
84
+ def has_git?
85
+ %x{ git --version }
86
+ $?.success?
87
+ end
88
+
89
+ def git(args)
90
+ return "" unless has_git?
91
+ flattened_args = [args].flatten.compact.join(" ")
92
+ %x{ git #{flattened_args} 2>&1 }.strip
93
+ end
94
+
95
+ def has_git_remote?(remote)
96
+ git('remote').split("\n").include?(remote) && $?.success?
97
+ end
98
+
99
+ def create_git_remote(remote, url)
100
+ return if has_git_remote? remote
101
+ git "remote add #{remote} #{url}"
102
+ display "Git remote #{remote} added" if $?.success?
103
+ end
104
+
105
+ def update_git_remote(remote, url)
106
+ return unless has_git_remote? remote
107
+ git "remote set-url #{remote} #{url}"
108
+ display "Git remote #{remote} updated" if $?.success?
109
+ end
110
+
111
+ @@kb = 1024
112
+ @@mb = 1024 * @@kb
113
+ @@gb = 1024 * @@mb
114
+ def format_bytes(amount)
115
+ amount = amount.to_i
116
+ return '(empty)' if amount == 0
117
+ return amount if amount < @@kb
118
+ return "#{(amount / @@kb).round}k" if amount < @@mb
119
+ return "#{(amount / @@mb).round}M" if amount < @@gb
120
+ return "#{(amount / @@gb).round}G"
121
+ end
122
+
123
+ def json_decode(json)
124
+ MultiJson.load(json)
125
+ rescue MultiJson::ParseError
126
+ nil
127
+ end
128
+
129
+ def with_tty(&block)
130
+ return unless $stdin.isatty
131
+ begin
132
+ yield
133
+ rescue
134
+ # fails on windows
135
+ end
136
+ end
137
+
138
+ def action(message, options={})
139
+ message = "#{message} in organization #{org}" if options[:org]
140
+ display("#{message}... ", false)
141
+ Pebbles::Helpers.error_with_failure = true
142
+ ret = yield
143
+ Pebbles::Helpers.error_with_failure = false
144
+ display((options[:success] || "done"), false)
145
+ if @status
146
+ display(", #{@status}", false)
147
+ @status = nil
148
+ end
149
+ display
150
+ ret
151
+ end
152
+
153
+ def confirm_command(app_to_confirm = app, message=nil)
154
+ if confirmed_app = Pebbles::Command.current_options[:confirm]
155
+ unless confirmed_app == app_to_confirm
156
+ raise(Pebbles::Command::CommandFailed, "Confirmed app #{confirmed_app} did not match the selected app #{app_to_confirm}.")
157
+ end
158
+ return true
159
+ else
160
+ display
161
+ message ||= "WARNING: Destructive Action\nThis command will affect the app: #{app_to_confirm}"
162
+ message << "\nTo proceed, type \"#{app_to_confirm}\" or re-run this command with --confirm #{app_to_confirm}"
163
+ output_with_bang(message)
164
+ display
165
+ display "> ", false
166
+ if ask.downcase != app_to_confirm
167
+ error("Confirmation did not match #{app_to_confirm}. Aborted.")
168
+ else
169
+ true
170
+ end
171
+ end
172
+ end
173
+
174
+ def format_error(error, message='Pebblescape client internal error.')
175
+ formatted_error = []
176
+ formatted_error << " ! #{message}"
177
+ formatted_error << ''
178
+ formatted_error << " Error: #{error.message} (#{error.class})"
179
+ command = ARGV.map do |arg|
180
+ if arg.include?(' ')
181
+ arg = %{"#{arg}"}
182
+ else
183
+ arg
184
+ end
185
+ end.join(' ')
186
+ formatted_error << " Command: pebbles #{command}"
187
+ require 'pebbles/auth'
188
+ unless Pebbles::Auth.host == Pebbles::Auth.default_host
189
+ formatted_error << " Host: #{Pebbles::Auth.host}"
190
+ end
191
+ if http_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY']
192
+ formatted_error << " HTTP Proxy: #{http_proxy}"
193
+ end
194
+ if https_proxy = ENV['https_proxy'] || ENV['HTTPS_PROXY']
195
+ formatted_error << " HTTPS Proxy: #{https_proxy}"
196
+ end
197
+ formatted_error << " Version: #{Pebbles.user_agent}"
198
+ formatted_error << "\n"
199
+ formatted_error.join("\n")
200
+ end
201
+
202
+ def styled_header(header)
203
+ display("=== #{header}")
204
+ end
205
+
206
+ # produces a printf formatter line for an array of items
207
+ # if an individual line item is an array, it will create columns
208
+ # that are lined-up
209
+ #
210
+ # line_formatter(["foo", "barbaz"]) # => "%-6s"
211
+ # line_formatter(["foo", "barbaz"], ["bar", "qux"]) # => "%-3s %-6s"
212
+ #
213
+ def line_formatter(array)
214
+ if array.any? {|item| item.is_a?(Array)}
215
+ cols = []
216
+ array.each do |item|
217
+ if item.is_a?(Array)
218
+ item.each_with_index { |val,idx| cols[idx] = [cols[idx]||0, (val || '').length].max }
219
+ end
220
+ end
221
+ cols.map { |col| "%-#{col}s" }.join(" ")
222
+ else
223
+ "%s"
224
+ end
225
+ end
226
+
227
+ def styled_array(array, options={})
228
+ fmt = line_formatter(array)
229
+ array = array.sort unless options[:sort] == false
230
+ array.each do |element|
231
+ display((fmt % element).rstrip)
232
+ end
233
+ display
234
+ end
235
+
236
+ def styled_hash(hash, keys=nil)
237
+ max_key_length = hash.keys.map {|key| key.to_s.length}.max + 2
238
+ keys ||= hash.keys.sort {|x,y| x.to_s <=> y.to_s}
239
+ keys.each do |key|
240
+ case value = hash[key]
241
+ when Array
242
+ if value.empty?
243
+ next
244
+ else
245
+ elements = value.sort {|x,y| x.to_s <=> y.to_s}
246
+ display("#{key}: ".ljust(max_key_length), false)
247
+ display(elements[0])
248
+ elements[1..-1].each do |element|
249
+ display("#{' ' * max_key_length}#{element}")
250
+ end
251
+ if elements.length > 1
252
+ display
253
+ end
254
+ end
255
+ when nil
256
+ next
257
+ else
258
+ display("#{key}: ".ljust(max_key_length), false)
259
+ display(value)
260
+ end
261
+ end
262
+ end
263
+
264
+ def flatten_hash(hash, key)
265
+ hash[key].each do |k, v|
266
+ hash["#{key}_#{k}"] = v
267
+ end
268
+
269
+ hash.delete(key)
270
+ end
271
+
272
+ def styled_error(error, message='Pebblescape client internal error.')
273
+ if Pebbles::Helpers.error_with_failure
274
+ display("failed")
275
+ Pebbles::Helpers.error_with_failure = false
276
+ end
277
+ $stderr.puts(format_error(error, message))
278
+ end
279
+
280
+ def has_http_git_entry_in_netrc
281
+ Auth.netrc && Auth.netrc[Auth.http_git_host]
282
+ end
283
+ end
284
+ end