pebblescape 0.0.1

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.
@@ -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