pebblescape 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,225 @@
1
+ require "pebbles/command/base"
2
+
3
+ # manage apps (create, destroy)
4
+ #
5
+ class Pebbles::Command::Apps < Pebbles::Command::Base
6
+
7
+ # apps
8
+ #
9
+ # list your apps
10
+ #
11
+ #Example:
12
+ #
13
+ # $ pebbles apps
14
+ # === My Apps
15
+ # example
16
+ # example2
17
+ #
18
+ def index
19
+ validate_arguments!
20
+
21
+ apps = api.get_apps.body
22
+
23
+ unless apps.empty?
24
+ styled_header("My Apps")
25
+ styled_array(apps.map { |app| app_name(app) })
26
+ else
27
+ display("You have no apps.")
28
+ end
29
+ end
30
+
31
+ alias_command "list", "apps"
32
+
33
+ # apps:info
34
+ #
35
+ # show detailed app information
36
+ #
37
+ # -s, --shell # output more shell friendly key/value pairs
38
+ #
39
+ #Examples:
40
+ #
41
+ # $ pebbles apps:info
42
+ # === example
43
+ # Git URL: https://git.pebblescape.com/example.git
44
+ # Repo Size: 5M
45
+ # ...
46
+ #
47
+ # $ pebbles apps:info --shell
48
+ # git_url=https://git.pebblescape.com/example.git
49
+ # repo_size=5000000
50
+ # ...
51
+ #
52
+ def info
53
+ validate_arguments!
54
+ app_data = api.get_app(app).body
55
+
56
+ unless options[:shell]
57
+ styled_header(app_data["name"])
58
+ end
59
+
60
+ if options[:shell]
61
+ app_data['git_url'] = git_url(app_data['name'])
62
+ if app_data['domain_name']
63
+ app_data['domain_name'] = app_data['domain_name']['domain']
64
+ end
65
+ app_data['owner'].delete('id')
66
+ flatten_hash(app_data, 'owner')
67
+ app_data.keys.sort_by { |a| a.to_s }.each do |key|
68
+ hputs("#{key}=#{app_data[key]}")
69
+ end
70
+ else
71
+ data = {}
72
+
73
+ if app_data["create_status"] && app_data["create_status"] != "complete"
74
+ data["Create Status"] = app_data["create_status"]
75
+ end
76
+
77
+ data["Git URL"] = git_url(app_data['name'])
78
+
79
+
80
+ if app_data["owner"]
81
+ data["Owner Email"] = app_data["owner"]["email"]
82
+ data["Owner"] = app_data["owner"]["name"]
83
+ end
84
+ data["Repo Size"] = format_bytes(app_data["repo_size"]) if app_data["repo_size"]
85
+ data["Build Size"] = format_bytes(app_data["build_size"]) if app_data["build_size"]
86
+ data["Web URL"] = app_data["web_url"]
87
+
88
+ styled_hash(data)
89
+ end
90
+ end
91
+
92
+ alias_command "info", "apps:info"
93
+
94
+ # apps:create [NAME]
95
+ #
96
+ # create a new app
97
+ #
98
+ # --addons ADDONS # a comma-delimited list of addons to install
99
+ # -b, --buildpack BUILDPACK # a buildpack url to use for this app
100
+ # -n, --no-remote # don't create a git remote
101
+ # -r, --remote REMOTE # the git remote to create, default "pebbles"
102
+ # --ssh-git # Use SSH git protocol
103
+ # --http-git # HIDDEN: Use HTTP git protocol
104
+ #
105
+ #Examples:
106
+ #
107
+ # $ pebbles apps:create
108
+ # Creating floating-dragon-42... done, stack is cedar
109
+ # http://floating-dragon-42.pebblesinspace.com/ | https://git.pebblesinspace.com/floating-dragon-42.git
110
+ #
111
+ # # specify a name
112
+ # $ pebbles apps:create example
113
+ # Creating example... done, stack is cedar
114
+ # http://example.pebblesinspace.com/ | https://git.pebblesinspace.com/example.git
115
+ #
116
+ # # create a staging app
117
+ # $ pebbles apps:create example-staging --remote staging
118
+ #
119
+ def create
120
+ name = shift_argument || options[:app] || ENV['PEBBLES_APP']
121
+ validate_arguments!
122
+
123
+ params = {
124
+ "name" => name,
125
+ }
126
+
127
+ info = api.post_app(params).body
128
+
129
+ begin
130
+ action("Creating #{info['name']}") do
131
+ if info['create_status'] == 'creating'
132
+ Timeout::timeout(options[:timeout].to_i) do
133
+ loop do
134
+ break if api.get_app(info['name']).body['create_status'] == 'complete'
135
+ sleep 1
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ # (options[:addons] || "").split(",").each do |addon|
142
+ # addon.strip!
143
+ # action("Adding #{addon} to #{info["name"]}") do
144
+ # api.post_addon(info["name"], addon)
145
+ # end
146
+ # end
147
+
148
+ if buildpack = options[:buildpack]
149
+ api.put_config_vars(info["name"], "BUILDPACK_URL" => buildpack)
150
+ display("BUILDPACK_URL=#{buildpack}")
151
+ end
152
+
153
+ hputs([ info["web_url"], git_url(info['name']) ].join(" | "))
154
+ rescue Timeout::Error
155
+ hputs("Timed Out! Run `pebbles status` to check for known platform issues.")
156
+ end
157
+
158
+ unless options[:no_remote].is_a? FalseClass
159
+ create_git_remote(options[:remote] || "pebbles", git_url(info['name']))
160
+ end
161
+ end
162
+
163
+ alias_command "create", "apps:create"
164
+
165
+ # apps:destroy --app APP
166
+ #
167
+ # permanently destroy an app
168
+ #
169
+ #Example:
170
+ #
171
+ # $ pebbles apps:destroy -a example --confirm example
172
+ # Destroying example (including all add-ons)... done
173
+ #
174
+ def destroy
175
+ @app = shift_argument || options[:app] || options[:confirm]
176
+ validate_arguments!
177
+
178
+ unless @app
179
+ error("Usage: pebbles apps:destroy --app APP\nMust specify APP to destroy.")
180
+ end
181
+
182
+ api.get_app(@app) # fail fast if no access or doesn't exist
183
+
184
+ message = "WARNING: Potentially Destructive Action\nThis command will destroy #{@app} (including all add-ons)."
185
+ if confirm_command(@app, message)
186
+ action("Destroying #{@app} (including all add-ons)") do
187
+ api.delete_app(@app)
188
+ if remotes = git_remotes(Dir.pwd)
189
+ remotes.each do |remote_name, remote_app|
190
+ next if @app != remote_app
191
+ git "remote rm #{remote_name}"
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end
197
+
198
+ alias_command "destroy", "apps:destroy"
199
+ alias_command "apps:delete", "apps:destroy"
200
+
201
+ # apps:open --app APP
202
+ #
203
+ # open the app in a web browser
204
+ #
205
+ #Example:
206
+ #
207
+ # $ heroku apps:open
208
+ # Opening example... done
209
+ #
210
+ def open
211
+ path = shift_argument
212
+ validate_arguments!
213
+
214
+ app_data = api.get_app(app).body
215
+
216
+ url = [app_data['web_url'], path].join
217
+ launchy("Opening #{app}", url)
218
+ end
219
+
220
+ private
221
+
222
+ def app_name(app)
223
+ app["name"]
224
+ end
225
+ end
@@ -0,0 +1,85 @@
1
+ require "pebbles/command/base"
2
+
3
+ # authentication (login, logout)
4
+ #
5
+ class Pebbles::Command::Auth < Pebbles::Command::Base
6
+
7
+ # auth
8
+ #
9
+ # Authenticate, display token and current user
10
+ def index
11
+ validate_arguments!
12
+
13
+ Pebbles::Command::Help.new.send(:help_for_command, current_command)
14
+ end
15
+
16
+ # auth:login
17
+ #
18
+ # log in with your Pebblescape credentials
19
+ #
20
+ #Example:
21
+ #
22
+ # $ pebbles auth:login
23
+ # Enter your Pebblescape credentials:
24
+ # Email: email@example.com
25
+ # Password (typing will be hidden):
26
+ # Authentication successful.
27
+ #
28
+ def login
29
+ validate_arguments!
30
+
31
+ Pebbles::Auth.login
32
+ display "Authentication successful."
33
+ end
34
+
35
+ alias_command "login", "auth:login"
36
+
37
+ # auth:logout
38
+ #
39
+ # clear local authentication credentials
40
+ #
41
+ #Example:
42
+ #
43
+ # $ pebbles auth:logout
44
+ # Local credentials cleared.
45
+ #
46
+ def logout
47
+ validate_arguments!
48
+
49
+ Pebbles::Auth.logout
50
+ display "Local credentials cleared."
51
+ end
52
+
53
+ alias_command "logout", "auth:logout"
54
+
55
+ # auth:token
56
+ #
57
+ # display your api token
58
+ #
59
+ #Example:
60
+ #
61
+ # $ pebbles auth:token
62
+ # ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCD
63
+ #
64
+ def token
65
+ validate_arguments!
66
+
67
+ display Pebbles::Auth.api_key
68
+ end
69
+
70
+ # auth:whoami
71
+ #
72
+ # display your Pebblescape email address
73
+ #
74
+ #Example:
75
+ #
76
+ # $ pebbles auth:whoami
77
+ # email@example.com
78
+ #
79
+ def whoami
80
+ validate_arguments!
81
+
82
+ display Pebbles::Auth.user
83
+ end
84
+
85
+ end
@@ -0,0 +1,231 @@
1
+ require "fileutils"
2
+ require "pebbles/auth"
3
+ require "pebbles/command"
4
+
5
+ class Pebbles::Command::Base
6
+ include Pebbles::Helpers
7
+
8
+ def self.namespace
9
+ self.to_s.split("::").last.downcase
10
+ end
11
+
12
+ attr_reader :args
13
+ attr_reader :options
14
+
15
+ def initialize(args=[], options={})
16
+ @args = args
17
+ @options = options
18
+ end
19
+
20
+ def app
21
+ @app ||= Pebbles.app_name = if options[:confirm].is_a?(String)
22
+ if options[:app] && (options[:app] != options[:confirm])
23
+ error("Mismatch between --app and --confirm")
24
+ end
25
+ options[:confirm]
26
+ elsif options[:app].is_a?(String)
27
+ options[:app]
28
+ elsif ENV.has_key?('PEBBLES_APP')
29
+ ENV['PEBBLES_APP']
30
+ elsif app_from_dir = extract_app_in_dir(Dir.pwd)
31
+ app_from_dir
32
+ else
33
+ # raise instead of using error command to enable rescuing when app is optional
34
+ raise Pebbles::Command::CommandFailed.new("No app specified.\nRun this command from an app folder or specify which app to use with --app APP.") unless options[:ignore_no_app]
35
+ end
36
+ end
37
+
38
+ def api
39
+ Pebbles::Auth.api
40
+ end
41
+
42
+ protected
43
+
44
+ def self.inherited(klass)
45
+ unless klass == Pebbles::Command::Base
46
+ help = extract_help_from_caller(caller.first)
47
+
48
+ Pebbles::Command.register_namespace(
49
+ :name => klass.namespace,
50
+ :description => help.first
51
+ )
52
+ end
53
+ end
54
+
55
+ def self.method_added(method)
56
+ return if self == Pebbles::Command::Base
57
+ return if private_method_defined?(method)
58
+ return if protected_method_defined?(method)
59
+
60
+ help = extract_help_from_caller(caller.first)
61
+ resolved_method = (method.to_s == "index") ? nil : method.to_s
62
+ command = [ self.namespace, resolved_method ].compact.join(":")
63
+ banner = extract_banner(help) || command
64
+
65
+ Pebbles::Command.register_command(
66
+ :klass => self,
67
+ :method => method,
68
+ :namespace => self.namespace,
69
+ :command => command,
70
+ :banner => banner.strip,
71
+ :help => help.join("\n"),
72
+ :summary => extract_summary(help),
73
+ :description => extract_description(help),
74
+ :options => extract_options(help)
75
+ )
76
+
77
+ alias_command command.gsub(/_/, '-'), command if command =~ /_/
78
+ end
79
+
80
+ def self.alias_command(new, old)
81
+ raise "no such command: #{old}" unless Pebbles::Command.commands[old]
82
+ Pebbles::Command.command_aliases[new] = old
83
+ end
84
+
85
+ #
86
+ # Parse the caller format and identify the file and line number as identified
87
+ # in : http://www.ruby-doc.org/core/classes/Kernel.html#M001397. This will
88
+ # look for a colon followed by a digit as the delimiter. The biggest
89
+ # complication is windows paths, which have a colon after the drive letter.
90
+ # This regex will match paths as anything from the beginning to a colon
91
+ # directly followed by a number (the line number).
92
+ #
93
+ # Examples of the caller format :
94
+ # * c:/Ruby192/lib/.../lib/heroku/command/addons.rb:8:in `<module:Command>'
95
+ # * c:/Ruby192/lib/.../heroku-2.0.1/lib/heroku/command/pg.rb:96:in `<class:Pg>'
96
+ # * /Users/ph7/...../xray-1.1/lib/xray/thread_dump_signal_handler.rb:9
97
+ #
98
+ def self.extract_help_from_caller(line)
99
+ # pull out of the caller the information for the file path and line number
100
+ if line =~ /^(.+?):(\d+)/
101
+ extract_help($1, $2)
102
+ else
103
+ raise("unable to extract help from caller: #{line}")
104
+ end
105
+ end
106
+
107
+ def self.extract_help(file, line_number)
108
+ buffer = []
109
+ lines = Pebbles::Command.files[file]
110
+
111
+ (line_number.to_i-2).downto(0) do |i|
112
+ line = lines[i]
113
+ case line[0..0]
114
+ when ""
115
+ when "#"
116
+ buffer.unshift(line[1..-1])
117
+ else
118
+ break
119
+ end
120
+ end
121
+
122
+ buffer
123
+ end
124
+
125
+ def self.extract_banner(help)
126
+ help.first
127
+ end
128
+
129
+ def self.extract_summary(help)
130
+ extract_description(help).split("\n")[2].to_s.split("\n").first
131
+ end
132
+
133
+ def self.extract_description(help)
134
+ help.reject do |line|
135
+ line =~ /^\s+-(.+)#(.+)/
136
+ end.join("\n")
137
+ end
138
+
139
+ def self.extract_options(help)
140
+ help.select do |line|
141
+ line =~ /^\s+-(.+)#(.+)/
142
+ end.inject([]) do |options, line|
143
+ args = line.split('#', 2).first
144
+ args = args.split(/,\s*/).map {|arg| arg.strip}.sort.reverse
145
+ name = args.last.split(' ', 2).first[2..-1]
146
+ options << { :name => name, :args => args }
147
+ end
148
+ end
149
+
150
+ def current_command
151
+ Pebbles::Command.current_command
152
+ end
153
+
154
+ def extract_option(key)
155
+ options[key.dup.gsub('-','_').to_sym]
156
+ end
157
+
158
+ def invalid_arguments
159
+ Pebbles::Command.invalid_arguments
160
+ end
161
+
162
+ def shift_argument
163
+ Pebbles::Command.shift_argument
164
+ end
165
+
166
+ def validate_arguments!
167
+ Pebbles::Command.validate_arguments!
168
+ end
169
+
170
+ def extract_app_in_dir(dir)
171
+ return unless remotes = git_remotes(dir)
172
+
173
+ if remote = options[:remote]
174
+ remotes[remote]
175
+ elsif remote = extract_remote_from_git_config
176
+ remotes[remote]
177
+ else
178
+ apps = remotes.values.uniq
179
+ if apps.size == 1
180
+ apps.first
181
+ else
182
+ raise(Pebbles::Command::CommandFailed, "Multiple apps in folder and no app specified.\nSpecify app with --app APP.") unless options[:ignore_no_app]
183
+ end
184
+ end
185
+ end
186
+
187
+ def extract_remote_from_git_config
188
+ remote = git("config pebbles.remote")
189
+ remote == "" ? nil : remote
190
+ end
191
+
192
+ def git_url(app_name)
193
+ if options[:ssh_git]
194
+ "git@#{Pebbles::Auth.git_host}:#{app_name}.git"
195
+ else
196
+ unless has_http_git_entry_in_netrc
197
+ warn "WARNING: Incomplete credentials detected, git may not work with Pebblescape. Run `pebbles login` to update your credentials."
198
+ exit 1
199
+ end
200
+ "https://#{Pebbles::Auth.http_git_host}/#{app_name}.git"
201
+ end
202
+ end
203
+
204
+ def git_remotes(base_dir=Dir.pwd)
205
+ remotes = {}
206
+ original_dir = Dir.pwd
207
+ Dir.chdir(base_dir)
208
+
209
+ return unless File.exists?(".git")
210
+ git("remote -v").split("\n").each do |remote|
211
+ name, url, _ = remote.split(/\s/)
212
+ if url =~ /^git@#{Pebbles::Auth.git_host}(?:[\.\w]*):([\w\d-]+)\.git$/ ||
213
+ url =~ /^https:\/\/#{Pebbles::Auth.http_git_host}\/([\w\d-]+)\.git$/
214
+ remotes[name] = $1
215
+ end
216
+ end
217
+
218
+ Dir.chdir(original_dir)
219
+ if remotes.empty?
220
+ nil
221
+ else
222
+ remotes
223
+ end
224
+ end
225
+ end
226
+
227
+ module Pebbles::Command
228
+ unless const_defined?(:BaseWithApp)
229
+ BaseWithApp = Base
230
+ end
231
+ end