gemfury 0.12.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,18 @@
1
+ module Gemfury
2
+ class Client
3
+ module Filters
4
+
5
+ private
6
+ def ensure_ready!(*args)
7
+ # Ensure authorization
8
+ if args.include?(:authorization)
9
+ raise Unauthorized unless authenticated?
10
+ end
11
+ end
12
+
13
+ def authenticated?
14
+ self.user_api_key && !self.user_api_key.empty?
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,23 @@
1
+ module Gemfury
2
+ class Client
3
+ class Handle503 < Faraday::Middleware
4
+ def call(env)
5
+ # This prevents errors in ParseJson
6
+ @app.call(env).on_complete do |out|
7
+ out[:body] = '' if out[:status] == 503
8
+ end
9
+ end
10
+ end
11
+
12
+ class ParseJson < Faraday::Response::Middleware
13
+ def parse(body)
14
+ body =~ /\A\s*\z/ ? nil : MultiJson.decode(body)
15
+ end
16
+
17
+ def on_complete(response)
18
+ ok = response.request_headers['Accept'] =~ /json\z/
19
+ response.body = parse(response.body) if ok
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,13 @@
1
+ gem "progressbar", ">= 1.10.1", "< 2.0.0.pre"
2
+ gem "highline", ">= 1.6.0", "< 2.1.0.pre"
3
+ gem "thor", ">= 0.14.0", "< 1.1.0.pre"
4
+
5
+ require 'thor'
6
+ require 'yaml'
7
+ require 'highline'
8
+ require 'fileutils'
9
+
10
+ module Gemfury::Command; end
11
+
12
+ require 'gemfury/command/authorization'
13
+ require 'gemfury/command/app'
@@ -0,0 +1,425 @@
1
+ require 'progressbar'
2
+ require 'delegate'
3
+
4
+ class Gemfury::Command::App < Thor
5
+ include Gemfury::Command::Authorization
6
+ UserAgent = "Gemfury CLI #{Gemfury::VERSION}".freeze
7
+ PackageExtensions = %w(gem egg tar.gz tgz nupkg)
8
+
9
+ # Impersonation
10
+ class_option :as, :desc => 'Access an account other than your own'
11
+ class_option :api_token, :desc => 'API token to use for commands'
12
+
13
+ map "-v" => :version
14
+ desc "version", "Show Gemfury version", :hide => true
15
+ def version
16
+ shell.say Gemfury::VERSION
17
+ end
18
+
19
+ ### PACKAGE MANAGEMENT ###
20
+ option :public, :type => :boolean, :desc => "Create as public package"
21
+ option :quiet, :type => :boolean, :aliases => "-q", :desc => "Do not show progress bar", :default => false
22
+ desc "push FILE", "Upload a new version of a package"
23
+ def push(*gems)
24
+ with_checks_and_rescues do
25
+ push_files(:push, gems)
26
+ end
27
+ end
28
+
29
+ desc "list", "List your packages"
30
+ def list
31
+ with_checks_and_rescues do
32
+ gems = client.list
33
+ shell.say "\n*** GEMFURY PACKAGES ***\n\n"
34
+
35
+ va = [ %w{ name kind version privacy } ]
36
+ gems.each do |g|
37
+ va << [ g['name'], g['language'],
38
+ g.dig('latest_version', 'version') || 'beta',
39
+ g['private'] ? 'private' : 'public ' ]
40
+ end
41
+
42
+ shell.print_table(va)
43
+ end
44
+ end
45
+
46
+ desc "versions NAME", "List all the package versions"
47
+ def versions(gem_name)
48
+ with_checks_and_rescues do
49
+ versions = client.versions(gem_name)
50
+ shell.say "\n*** #{gem_name.capitalize} Versions ***\n\n"
51
+
52
+ va = []
53
+ va = [ %w{ version uploaded_by uploaded } ]
54
+ versions.each do |v|
55
+ uploaded = time_ago(Time.parse(v['created_at']).getlocal)
56
+ va << [ v['version'], v['created_by']['name'], uploaded ]
57
+ end
58
+
59
+ shell.print_table(va)
60
+ end
61
+ end
62
+
63
+ desc "yank NAME", "Delete a package version"
64
+ method_options %w(version -v) => :required
65
+ def yank(gem_name)
66
+ with_checks_and_rescues do
67
+ version = options[:version]
68
+ client.yank_version(gem_name, version)
69
+ shell.say "\n*** Yanked #{gem_name}-#{version} ***\n\n"
70
+ end
71
+ end
72
+
73
+ ### AUTHENTICATION ###
74
+ desc "logout", "Remove Gemfury credentials"
75
+ def logout
76
+ if !has_credentials?
77
+ shell.say "You are logged out"
78
+ elsif shell.yes? "Are you sure you want to log out? [yN]"
79
+ with_checks_and_rescues { client.logout }
80
+ wipe_credentials!
81
+ shell.say "You have been logged out"
82
+ end
83
+ end
84
+
85
+ desc "login", "Save Gemfury credentials"
86
+ def login
87
+ with_checks_and_rescues do
88
+ me = client.account_info['name']
89
+ shell.say %Q(You are logged in as "#{me}"), :green
90
+ end
91
+ end
92
+
93
+ desc "whoami", "Show current user"
94
+ def whoami
95
+ has_credentials? ? self.login : begin
96
+ shell.say %Q(You are not logged in), :green
97
+ end
98
+ end
99
+
100
+ desc "accounts", "Show info about your Gemfury accounts"
101
+ def accounts
102
+ with_checks_and_rescues do
103
+ accounts = client.accounts
104
+
105
+ va = [ %w{ name kind permission } ]
106
+ accounts.each do |a|
107
+ va << [ a['name'], a['type'], a['viewer_permission'].downcase ]
108
+ end
109
+
110
+ shell.print_table(va)
111
+ end
112
+ end
113
+
114
+ ### COLLABORATION MANAGEMENT ###
115
+ map "sharing:add" => 'sharing_add'
116
+ map "sharing:remove" => 'sharing_remove'
117
+
118
+ desc "sharing", "List collaborators"
119
+ def sharing
120
+ with_checks_and_rescues do
121
+ account_info = client.account_info
122
+ me = account_info['username']
123
+
124
+ collaborators = client.list_collaborators
125
+ if collaborators.empty?
126
+ shell.say %Q(You (#{me}) are the only collaborator\n), :green
127
+ else
128
+ shell.say %Q(\n*** Collaborators for "#{me}" ***\n), :green
129
+
130
+ va = [ %w{ username permission } ]
131
+
132
+ if account_info['type'] == 'user'
133
+ va << [ me, 'owner' ]
134
+ end
135
+
136
+ collaborators.each { |c| va << [ c['username'], c['permission'] ] }
137
+
138
+ shell.print_table(va)
139
+ end
140
+ end
141
+ end
142
+
143
+ desc "sharing:add EMAIL", "Add a collaborator"
144
+ def sharing_add(username)
145
+ with_checks_and_rescues do
146
+ client.add_collaborator(username)
147
+ shell.say "Invited #{username} as a collaborator"
148
+ end
149
+ end
150
+
151
+ desc "sharing:remove EMAIL", "Remove a collaborator"
152
+ def sharing_remove(username)
153
+ with_checks_and_rescues do
154
+ client.remove_collaborator(username)
155
+ shell.say "Removed #{username} as a collaborator"
156
+ end
157
+ end
158
+
159
+ ### MIGRATION (Pushing directories) ###
160
+ desc "migrate DIR", "Upload all packages within a directory"
161
+ def migrate(*paths)
162
+ with_checks_and_rescues do
163
+ gem_paths = Dir.glob(paths.map do |p|
164
+ if File.directory?(p)
165
+ PackageExtensions.map { |ext| "#{p}/**/*.#{ext}" }
166
+ elsif File.file?(p)
167
+ p
168
+ else
169
+ nil
170
+ end
171
+ end.flatten.compact)
172
+
173
+ if gem_paths.empty?
174
+ die!("Problem: No valid packages found", nil, :migrate)
175
+ else
176
+ shell.say "Found the following packages:"
177
+ gem_paths.each { |p| shell.say " #{File.basename(p)}" }
178
+ if shell.yes? "Upload these files to Gemfury? [yN]", :green
179
+ push_files(:migrate, gem_paths)
180
+ end
181
+ end
182
+ end
183
+ end
184
+
185
+ ### GIT REPOSITORY MANAGEMENT ###
186
+ map "git:list" => 'git_list'
187
+ map "git:reset" => 'git_reset'
188
+ map "git:rename" => 'git_rename'
189
+ map "git:rebuild" => 'git_rebuild'
190
+
191
+ desc "git:list", "List Git repositories"
192
+ def git_list
193
+ with_checks_and_rescues do
194
+ repos = client.git_repos['repos']
195
+ shell.say "\n*** GEMFURY GIT REPOS ***\n\n"
196
+ names = repos.map { |r| r['name'] }
197
+ names.sort.each { |n| shell.say(n) }
198
+ end
199
+ end
200
+
201
+ desc "git:rename", "Rename a Git repository"
202
+ def git_rename(repo, new_name)
203
+ with_checks_and_rescues do
204
+ client.git_update(repo, :repo => { :name => new_name })
205
+ shell.say "Renamed #{repo} repository to #{new_name}\n"
206
+ end
207
+ end
208
+
209
+ desc "git:reset", "Remove a Git repository"
210
+ def git_reset(repo)
211
+ with_checks_and_rescues do
212
+ client.git_reset(repo)
213
+ shell.say "\n*** Yanked #{repo} repository ***\n\n"
214
+ end
215
+ end
216
+
217
+ desc "git:rebuild", "Rebuild a Git repository"
218
+ method_options %w(revision -r) => :string
219
+ def git_rebuild(repo)
220
+ with_checks_and_rescues do
221
+ params = { :revision => options[:revision] }
222
+ shell.say "\n*** Rebuilding #{repo} repository ***\n\n"
223
+ shell.say client.git_rebuild(repo, :build => params)
224
+ end
225
+ end
226
+
227
+ ### GIT REPOSITORY BUILD CONFIG ###
228
+ map 'git:config' => 'git_config'
229
+ map 'git:config:set' => 'git_config_set'
230
+ map 'git:config:unset' => 'git_config_unset'
231
+
232
+ desc "git:config", "List Git repository's build environment"
233
+ def git_config(repo)
234
+ with_checks_and_rescues do
235
+ vars = client.git_config(repo)['config_vars']
236
+ shell.say "*** #{repo} build config ***\n"
237
+ shell.print_table(vars.map { |kv|
238
+ ["#{kv[0]}:", kv[1]]
239
+ })
240
+ end
241
+ end
242
+
243
+ desc "git:config:set", "Update Git repository's build environment"
244
+ def git_config_set(repo, *vars)
245
+ with_checks_and_rescues do
246
+ updates = Hash[vars.map { |v| v.split("=", 2) }]
247
+ client.git_config_update(repo, updates)
248
+ shell.say "Updated #{repo} build config"
249
+ end
250
+ end
251
+
252
+ desc "git:config:unset", "Remove variables from Git repository's build environment"
253
+ def git_config_unset(repo, *vars)
254
+ with_checks_and_rescues do
255
+ updates = Hash[vars.map { |v| [v, nil] }]
256
+ client.git_config_update(repo, updates)
257
+ shell.say "Updated #{repo} build config"
258
+ end
259
+ end
260
+
261
+ private
262
+ def client
263
+ opts = {}
264
+ opts[:user_api_key] = @user_api_key if @user_api_key
265
+ opts[:account] = options[:as] if options[:as]
266
+ client = Gemfury::Client.new(opts)
267
+ client.user_agent = UserAgent
268
+ return client
269
+ end
270
+
271
+ def with_checks_and_rescues(&block)
272
+ @user_api_key = options[:api_token] if options[:api_token]
273
+ with_authorization(&block)
274
+ rescue Gemfury::InvalidGemVersion => e
275
+ shell.say "You have a deprecated Gemfury client", :red
276
+ if shell.yes? "Would you like to update it now? [yN]"
277
+ exec("gem update gemfury")
278
+ else
279
+ shell.say %q(No problem. You can also run "gem update gemfury")
280
+ end
281
+ rescue Gemfury::Conflict => e
282
+ die!("Oops! Locked for another user. Try again later.", e)
283
+ rescue Gemfury::Forbidden => e
284
+ die!("Oops! You're not allowed to access this", e)
285
+ rescue Gemfury::NotFound => e
286
+ die!("Oops! Doesn't look like this exists", e)
287
+ rescue Gemfury::Error => e
288
+ die!("Oops! %s" % e.message, e)
289
+ rescue StandardError => e
290
+ die!("Oops! Something went wrong. Please contact support.", e)
291
+ end
292
+
293
+ def push_files(command, gem_paths)
294
+ files = gem_paths.map do |g|
295
+ g.is_a?(String) ? File.new(g) : g rescue nil
296
+ end.compact
297
+
298
+ if !options[:quiet] && !shell.mute? && $stdout.tty?
299
+ files = files.map { |g| ProgressIO.new(g) }
300
+ end
301
+
302
+ if files.empty?
303
+ die!("Problem: No valid packages found", nil, command)
304
+ end
305
+
306
+ push_options = { }
307
+ unless options[:public].nil?
308
+ push_options[:public] = options[:public]
309
+ end
310
+
311
+ error_ex = nil
312
+
313
+ files.each do |file|
314
+ show_bar = file.is_a?(ProgressIO) && file.show_bar?
315
+ title = "Uploading #{File.basename(file.path)} "
316
+
317
+ begin
318
+ if show_bar
319
+ begin
320
+ client.push_gem(file, push_options)
321
+ ensure
322
+ shell.say "\e[A\e[0K", nil, false
323
+ shell.say title
324
+ end
325
+ else
326
+ shell.say title
327
+ client.push_gem(file, push_options)
328
+ end
329
+
330
+ shell.say "- done"
331
+ rescue Gemfury::CorruptGemFile => e
332
+ shell.say "- problem processing this package", :red
333
+ error_ex = e
334
+ rescue Gemfury::DupeVersion => e
335
+ shell.say "- this version already exists", :red
336
+ error_ex = e
337
+ rescue Gemfury::TimeoutError, Errno::EPIPE => e
338
+ shell.say "- this file is too much to handle", :red
339
+ shell.say " Visit http://www.gemfury.com/large-package for more info"
340
+ error_ex = e
341
+ rescue => e
342
+ shell.say "- oops", :red
343
+ error_ex = e
344
+ end
345
+ end
346
+
347
+ unless error_ex.nil?
348
+ die!('There was a problem uploading at least 1 package', error_ex)
349
+ end
350
+ end
351
+
352
+ C50K = 50000
353
+
354
+ class ProgressIO < SimpleDelegator
355
+ attr_reader :content_type, :original_filename, :local_path
356
+
357
+ def initialize(filename_or_io, content_type = 'application/octet-stream', fname = nil)
358
+ io = filename_or_io
359
+ local_path = ''
360
+
361
+ if io.respond_to? :read
362
+ local_path = filename_or_io.respond_to?(:path) ? filename_or_io.path : 'local.path'
363
+ else
364
+ io = File.open(filename_or_io)
365
+ local_path = filename_or_io
366
+ end
367
+
368
+ fname ||= local_path
369
+
370
+ @content_type = content_type
371
+ @original_filename = File.basename(fname)
372
+ @local_path = local_path
373
+
374
+ if io.respond_to? :size
375
+ filesize = io.size
376
+ else
377
+ filesize = io.stat.size
378
+ end
379
+
380
+ if filesize > C50K
381
+ title = 'Uploading %s ' % File.basename(fname)
382
+ @bar = ProgressBar.create(:title => title, :total => filesize)
383
+ else
384
+ @bar = nil
385
+ end
386
+
387
+ super(io)
388
+ end
389
+
390
+ def show_bar?
391
+ @bar != nil
392
+ end
393
+
394
+ def read(length)
395
+ buf = __getobj__.read(length)
396
+ unless @bar.nil? || buf.nil?
397
+ @bar.progress += buf.bytesize
398
+ end
399
+
400
+ buf
401
+ end
402
+ end
403
+
404
+ def die!(msg, err = nil, command = nil)
405
+ shell.say msg, :red
406
+ help(command) if command
407
+ shell.say %Q(#{err.class.name}: #{err}\n#{err.backtrace.join("\n")}) if err && ENV['DEBUG']
408
+ exit(1)
409
+ end
410
+
411
+ def time_ago(tm)
412
+ ago = tm.strftime('%F %R')
413
+
414
+ in_secs = Time.now - tm
415
+ if in_secs < 60
416
+ ago += ' (~ %ds ago)' % in_secs
417
+ elsif in_secs < 3600
418
+ ago += ' (~ %sm ago)' % (in_secs / 60).floor
419
+ elsif in_secs < (3600 * 24)
420
+ ago += ' (~ %sh ago)' % (in_secs / 3600).floor
421
+ end
422
+
423
+ ago
424
+ end
425
+ end