package_cloud 0.0.3 → 0.3.15
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.
- checksums.yaml +7 -0
- data/README.md +506 -16
- data/bin/package_cloud +1 -1
- data/lib/package_cloud/auth.rb +2 -1
- data/lib/package_cloud/cli/distro.rb +51 -0
- data/lib/package_cloud/cli/entry.rb +397 -0
- data/lib/package_cloud/cli/gpg_key.rb +76 -0
- data/lib/package_cloud/cli/master_token.rb +75 -0
- data/lib/package_cloud/cli/read_token.rb +39 -0
- data/lib/package_cloud/cli/repository.rb +52 -0
- data/lib/package_cloud/cli.rb +49 -16
- data/lib/package_cloud/client.rb +44 -3
- data/lib/package_cloud/config_file.rb +69 -14
- data/lib/package_cloud/gpg_key.rb +13 -0
- data/lib/package_cloud/master_token.rb +19 -0
- data/lib/package_cloud/read_token.rb +14 -0
- data/lib/package_cloud/repository.rb +187 -6
- data/lib/package_cloud/util.rb +17 -0
- data/lib/package_cloud/validator.rb +118 -0
- data/lib/package_cloud/version.rb +5 -1
- data/lib/package_cloud.rb +6 -0
- data/markdown_lint_config.rb +2 -0
- data/package_cloud.gemspec +14 -10
- metadata +152 -96
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
require "cgi"
|
|
2
|
+
require "uri"
|
|
3
|
+
|
|
4
|
+
module PackageCloud
|
|
5
|
+
module CLI
|
|
6
|
+
JAVA_EXTS = ["jar", "aar", "war"]
|
|
7
|
+
PY_EXTS = ["gz", "bz2", "z", "tar", "egg-info", "whl", "egg"]
|
|
8
|
+
ANYFILE_EXTS = ["asc"]
|
|
9
|
+
NODE_EXTS = []
|
|
10
|
+
AMBIGUOUS_EXTS = ["tgz", "apk", "zip"]
|
|
11
|
+
SUPPORTED_EXTS = JAVA_EXTS + PY_EXTS + ANYFILE_EXTS + AMBIGUOUS_EXTS + ["gem", "deb", "rpm", "dsc"]
|
|
12
|
+
|
|
13
|
+
class Entry < Base
|
|
14
|
+
desc "repository SUBCMD ...ARGS", "manage repositories"
|
|
15
|
+
subcommand "repository", Repository
|
|
16
|
+
|
|
17
|
+
desc "distro SUBCMD ...ARGS", "manage repositories"
|
|
18
|
+
subcommand "distro", Distro
|
|
19
|
+
|
|
20
|
+
desc "master_token SUBCMD ...ARGS", "manage master tokens"
|
|
21
|
+
subcommand "master_token", MasterToken
|
|
22
|
+
|
|
23
|
+
desc "read_token SUBCMD ...ARGS", "manage read tokens"
|
|
24
|
+
subcommand "read_token", ReadToken
|
|
25
|
+
|
|
26
|
+
desc "gpg_key SUBCMD ...ARGS", "manage GPG keys"
|
|
27
|
+
subcommand "gpg_key", GpgKey
|
|
28
|
+
|
|
29
|
+
desc "promote user/repo[/distro/version] [@scope/]package_name user/destination_repo",
|
|
30
|
+
"promotes a package from user/repo [in dist/version] to user/destination_repo [also in dist/version]"
|
|
31
|
+
def promote(source_repo_desc, package_name, dest_repo_desc)
|
|
32
|
+
repo_name = source_repo_desc.split("/")[0..1].join("/")
|
|
33
|
+
dist = expand_dist_shortcut(source_repo_desc.split("/")[2..3].join("/"))
|
|
34
|
+
|
|
35
|
+
dest_repo_name = expand_dist_shortcut(dest_repo_desc.split("/")[0..1].join("/"))
|
|
36
|
+
|
|
37
|
+
if dist == "" && package_name =~ /\.gem$/
|
|
38
|
+
dist = "gems"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
print "Looking for source repository at #{repo_name}... "
|
|
42
|
+
src_repo = client.repository(source_repo_desc)
|
|
43
|
+
print "success!\n"
|
|
44
|
+
|
|
45
|
+
print "Looking for destination repository at #{dest_repo_name}... "
|
|
46
|
+
repo = client.repository(dest_repo_desc)
|
|
47
|
+
print "success!\n"
|
|
48
|
+
|
|
49
|
+
if package_name.include?('@')
|
|
50
|
+
scope, unscoped_package_name = package_scope(package_name)
|
|
51
|
+
|
|
52
|
+
print "Attempting to promote scoped package #{scope}/#{unscoped_package_name} from #{repo_name}/#{dist} to #{dest_repo_name}..."
|
|
53
|
+
packages = src_repo.promote(dist, unscoped_package_name, dest_repo_name, scope)
|
|
54
|
+
else
|
|
55
|
+
print "Attempting to promote #{repo_name}/#{dist}/#{package_name} to #{dest_repo_name}..."
|
|
56
|
+
packages = src_repo.promote(dist, package_name, dest_repo_name)
|
|
57
|
+
end
|
|
58
|
+
puts "done!".color(:green)
|
|
59
|
+
|
|
60
|
+
if dist == "node/1"
|
|
61
|
+
puts "WARNING: This Node.js package will NOT be downloadable by clients until a dist tag is created. Read more: https://packagecloud.io/docs/#node_promote".color(:yellow)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
desc "yank user/repo[/distro/version] [@scope/]package_name",
|
|
66
|
+
"yank package from user/repo [in dist/version]"
|
|
67
|
+
def yank(repo_desc, package_name)
|
|
68
|
+
ARGV.clear # otherwise gets explodes
|
|
69
|
+
|
|
70
|
+
# strip os/dist
|
|
71
|
+
repo_name = repo_desc.split("/")[0..1].join("/")
|
|
72
|
+
dist = expand_dist_shortcut(repo_desc.split("/")[2..3].join("/"))
|
|
73
|
+
|
|
74
|
+
if dist == "" && package_name =~ /\.gem$/
|
|
75
|
+
dist = "gems"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
print "Looking for repository at #{repo_name}... "
|
|
79
|
+
repo = client.repository(repo_desc)
|
|
80
|
+
print "success!\n"
|
|
81
|
+
|
|
82
|
+
if package_name.include?('@')
|
|
83
|
+
scope, unscoped_package_name = package_scope(package_name)
|
|
84
|
+
print "Attempting to yank scoped package at #{repo_name}/#{dist} #{package_name}..."
|
|
85
|
+
repo.yank(dist, unscoped_package_name, scope)
|
|
86
|
+
else
|
|
87
|
+
print "Attempting to yank package at #{repo_name}/#{dist}/#{package_name}..."
|
|
88
|
+
repo.yank(dist, package_name)
|
|
89
|
+
end
|
|
90
|
+
puts "done!".color(:green)
|
|
91
|
+
|
|
92
|
+
if dist == "node/1"
|
|
93
|
+
puts "WARNING: Deleting Node.js packages can have unexpected side effects with dist tags. Read more: https://packagecloud.io/docs/#node_delete".color(:yellow)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
desc "push user/repo[/distro/version] /path/to/packages",
|
|
98
|
+
"Push package(s) to repository (in distro/version, if required). Optional settings shown above."
|
|
99
|
+
|
|
100
|
+
option "skip-file-ext-validation", :type => :boolean,
|
|
101
|
+
:desc => "Skip checking validation of the file extension. Package upload will be attempted even if the extension is unrecognized."
|
|
102
|
+
|
|
103
|
+
option "yes", :type => :boolean,
|
|
104
|
+
:desc => "Automatically answer 'yes' prompted during package push. Useful for automating uploads."
|
|
105
|
+
|
|
106
|
+
option "skip-errors", :type => :boolean,
|
|
107
|
+
:desc => "Skip errors encountered during a package push and continue pushing the next package."
|
|
108
|
+
|
|
109
|
+
option "skip-duplicates", :type => :boolean,
|
|
110
|
+
:desc => "Skip packages which already exist and continue pushing the next package."
|
|
111
|
+
|
|
112
|
+
option "coordinates", :type => :string,
|
|
113
|
+
:desc => "Specify the exact maven coordinates to use for a JAR. Useful for JARs without coordinates, 'fat JARs', and WARs."
|
|
114
|
+
|
|
115
|
+
option "upload-before-push",
|
|
116
|
+
:type => :boolean,
|
|
117
|
+
:desc => "Upload package before push",
|
|
118
|
+
:default => false
|
|
119
|
+
|
|
120
|
+
def push(repo, package_file, *package_files)
|
|
121
|
+
total_time = Benchmark.measure do
|
|
122
|
+
ARGV.clear # otherwise gets explodes
|
|
123
|
+
package_files << package_file
|
|
124
|
+
|
|
125
|
+
exts = package_files.map { |f| f.split(".").last }.uniq
|
|
126
|
+
|
|
127
|
+
if package_files.length > 1 && exts.length > 1
|
|
128
|
+
abort("You can't push multiple packages of different types at the same time.\nFor example, use *.deb to push all your debs at once.".color(:red))
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
invalid_packages = package_files.select do |f|
|
|
132
|
+
!SUPPORTED_EXTS.include?(f.split(".").last.downcase)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
if JAVA_EXTS.include?(exts.first.downcase) && options.has_key?("coordinates")
|
|
136
|
+
puts "Using coordinates #{options["coordinates"].color(:yellow)}"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
if !options.has_key?("skip-file-ext-validation") && invalid_packages.any?
|
|
140
|
+
message = "I don't know how to push these packages:\n\n".color(:red)
|
|
141
|
+
invalid_packages.each do |p|
|
|
142
|
+
message << " #{p}\n"
|
|
143
|
+
end
|
|
144
|
+
message << "\npackage_cloud only supports node.js, deb, gem, java, python, "\
|
|
145
|
+
"rpm, alpine, helm, anyfile (.zip, .asc) packages".color(:red)
|
|
146
|
+
abort(message)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
if !options.has_key?("yes") && exts.first == "gem" && package_files.length > 1
|
|
150
|
+
answer = get_valid("Are you sure you want to push #{package_files.length} packages? (y/n)") do |s|
|
|
151
|
+
s == "y" || s == "n"
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
if answer != "y"
|
|
155
|
+
abort("Aborting...".color(:red))
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
validator = Validator.new(client)
|
|
160
|
+
if PY_EXTS.include?(exts.first.downcase)
|
|
161
|
+
dist_id = validator.distribution_id(repo, package_files, 'py')
|
|
162
|
+
elsif ANYFILE_EXTS.include?(exts.first.downcase)
|
|
163
|
+
dist_id = validator.distribution_id(repo, package_files, 'anyfile')
|
|
164
|
+
elsif NODE_EXTS.include?(exts.first.downcase)
|
|
165
|
+
dist_id = validator.distribution_id(repo, package_files, 'node')
|
|
166
|
+
elsif JAVA_EXTS.include?(exts.first.downcase)
|
|
167
|
+
abort_if_snapshot!(package_files)
|
|
168
|
+
dist_id = validator.distribution_id(repo, package_files, 'jar')
|
|
169
|
+
elsif AMBIGUOUS_EXTS.include?(exts.first.downcase)
|
|
170
|
+
dist_id = validator.distribution_id_from_repo_url(repo, package_files)
|
|
171
|
+
else
|
|
172
|
+
dist_id = validator.distribution_id(repo, package_files, exts.first)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# strip os/dist
|
|
176
|
+
split_repo = repo.split("/")[0..1].join("/")
|
|
177
|
+
|
|
178
|
+
print "Looking for repository at #{split_repo}... "
|
|
179
|
+
client_repo = nil
|
|
180
|
+
measurement = Benchmark.measure do
|
|
181
|
+
client_repo = client.repository(split_repo)
|
|
182
|
+
end
|
|
183
|
+
print "success!\n"
|
|
184
|
+
$logger.debug("repository lookup request timing: #{measurement}")
|
|
185
|
+
|
|
186
|
+
package_files.each do |f|
|
|
187
|
+
url = nil
|
|
188
|
+
files = nil
|
|
189
|
+
urls = nil
|
|
190
|
+
ext = f.split(".").last
|
|
191
|
+
|
|
192
|
+
if ext == "dsc"
|
|
193
|
+
print "Checking source package #{f}... "
|
|
194
|
+
files = parse_and_verify_dsc(client_repo, f, dist_id)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
if options["upload-before-push"]
|
|
198
|
+
print "Uploading #{f}... "
|
|
199
|
+
upload = client_repo.create_upload
|
|
200
|
+
begin
|
|
201
|
+
response = RestClient::Request.execute(
|
|
202
|
+
:method => upload.fetch("form").fetch("method", "POST"),
|
|
203
|
+
:url => upload.fetch("form").fetch("url"),
|
|
204
|
+
:timeout => nil,
|
|
205
|
+
:payload => upload.fetch("form").fetch("data", {}).merge(
|
|
206
|
+
upload.fetch("form").fetch("file_input") => File.open(f, "r"),
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
url = response.headers.fetch(:location)
|
|
210
|
+
|
|
211
|
+
# Amazon S3 uploads return urlencoded keys in the Location
|
|
212
|
+
# header, and we want to return the decoded object key for
|
|
213
|
+
# API operations.
|
|
214
|
+
uri = URI.parse(url)
|
|
215
|
+
if uri.host.end_with?(".amazonaws.com")
|
|
216
|
+
uri.path = CGI.unescape(uri.path)
|
|
217
|
+
url = uri.to_s
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
if files
|
|
221
|
+
urls = files.map do |file|
|
|
222
|
+
response = RestClient::Request.execute(
|
|
223
|
+
:method => upload.fetch("form").fetch("method", "POST"),
|
|
224
|
+
:url => upload.fetch("form").fetch("url"),
|
|
225
|
+
:timeout => nil,
|
|
226
|
+
:payload => upload.fetch("form").fetch("data", {}).merge(
|
|
227
|
+
upload.fetch("form").fetch("file_input") => File.open(file, "r"),
|
|
228
|
+
)
|
|
229
|
+
)
|
|
230
|
+
source_url = response.headers.fetch(:location)
|
|
231
|
+
source_uri = URI.parse(source_url)
|
|
232
|
+
if source_uri.host.end_with?(".amazonaws.com")
|
|
233
|
+
source_uri.path = CGI.unescape(source_uri.path)
|
|
234
|
+
source_url = source_uri.to_s
|
|
235
|
+
end
|
|
236
|
+
source_url
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
rescue
|
|
240
|
+
print "error!\n".color(:red)
|
|
241
|
+
puts $!, *$!.backtrace, $!.response, $!.response.body
|
|
242
|
+
exit(1)
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
print "Pushing #{f}... "
|
|
247
|
+
measurement = Benchmark.measure do
|
|
248
|
+
if options.has_key?("skip-errors")
|
|
249
|
+
create_package_skip_errors(client_repo, f, url, dist_id, files, urls, ext, options["coordinates"])
|
|
250
|
+
elsif options.has_key?("skip-duplicates")
|
|
251
|
+
create_package_skip_duplicates(client_repo, f, url, dist_id, files, urls, ext, options["coordinates"])
|
|
252
|
+
else
|
|
253
|
+
create_package(client_repo, f, url, dist_id, files, urls, ext, options["coordinates"])
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
$logger.debug("create package request timing: #{measurement}")
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
$logger.debug("push command total timing: #{total_time}")
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
desc "version",
|
|
263
|
+
"print version information"
|
|
264
|
+
def version
|
|
265
|
+
puts "package_cloud CLI #{VERSION}\nSee https://packagecloud.io/docs#cli for more details."
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
private
|
|
269
|
+
def expand_dist_shortcut(dist)
|
|
270
|
+
case dist
|
|
271
|
+
when 'java'
|
|
272
|
+
'java/maven2'
|
|
273
|
+
when 'python'
|
|
274
|
+
'python/1'
|
|
275
|
+
when 'anyfile'
|
|
276
|
+
'anyfile/1'
|
|
277
|
+
when 'node'
|
|
278
|
+
'node/1'
|
|
279
|
+
else
|
|
280
|
+
dist
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def package_scope(package_name)
|
|
285
|
+
name_parts = package_name.split('/')
|
|
286
|
+
if name_parts.size != 2 || !package_name.include?('@')
|
|
287
|
+
abort("Could not determine scope for '#{package_name}', it should look like: @my-scope/package-1.0.tgz".color(:red))
|
|
288
|
+
end
|
|
289
|
+
scope = name_parts[0]
|
|
290
|
+
unscoped_package_name = name_parts[1]
|
|
291
|
+
[scope, unscoped_package_name]
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def abort_if_snapshot!(files)
|
|
295
|
+
if files.any? { |file| file.include?("-SNAPSHOT") }
|
|
296
|
+
abort("SNAPSHOT uploads are not supported by the CLI, please use Maven instead: https://packagecloud.io/docs#wagon-instructions")
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def create_package_skip_errors(client_repo, f, url, dist_id, files, urls, ext, coordinates=nil)
|
|
301
|
+
begin
|
|
302
|
+
client_repo.create_package(f, url, dist_id, files, urls, ext, coordinates=nil)
|
|
303
|
+
rescue RestClient::UnprocessableEntity => e
|
|
304
|
+
print "error (skipping):\n".color(:yellow)
|
|
305
|
+
json = JSON.parse(e.response)
|
|
306
|
+
json.each do |k,v|
|
|
307
|
+
puts "\n\t#{k}: #{v.join(", ")}\n"
|
|
308
|
+
end
|
|
309
|
+
puts ""
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def create_package_skip_duplicates(client_repo, f, url, dist_id, files, urls, ext, coordinates=nil)
|
|
314
|
+
begin
|
|
315
|
+
client_repo.create_package(f, url, dist_id, files, urls, ext, coordinates=nil)
|
|
316
|
+
rescue RestClient::UnprocessableEntity => e
|
|
317
|
+
json = JSON.parse(e.response)
|
|
318
|
+
if json == { "filename" => ["has already been taken"] }
|
|
319
|
+
print "skipping, already exists.\n".color(:yellow)
|
|
320
|
+
else
|
|
321
|
+
print "error:\n".color(:red)
|
|
322
|
+
json.each do |k,v|
|
|
323
|
+
if v.is_a? String
|
|
324
|
+
puts "\n\t#{k}: #{v}\n"
|
|
325
|
+
elsif v.is_a? Array
|
|
326
|
+
puts "\n\t#{k}: #{v.join(", ")}\n"
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
puts ""
|
|
330
|
+
exit(1)
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def create_package(client_repo, f, url, dist_id, files, urls, ext, coordinates=nil)
|
|
336
|
+
begin
|
|
337
|
+
client_repo.create_package(f, url, dist_id, files, urls, ext, coordinates)
|
|
338
|
+
rescue RestClient::UnprocessableEntity => e
|
|
339
|
+
print "error:\n".color(:red)
|
|
340
|
+
json = JSON.parse(e.response)
|
|
341
|
+
json.each do |k,v|
|
|
342
|
+
if v.is_a? String
|
|
343
|
+
puts "\n\t#{k}: #{v}\n"
|
|
344
|
+
elsif v.is_a? Array
|
|
345
|
+
puts "\n\t#{k}: #{v.join(", ")}\n"
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
puts ""
|
|
349
|
+
exit(1)
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def parse_and_verify_dsc(repo, f, dist_id)
|
|
354
|
+
files = repo.parse_dsc(f, dist_id)
|
|
355
|
+
dirname = File.dirname(f)
|
|
356
|
+
find_and_verify(dirname, files)
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def find_and_verify(dir, files)
|
|
360
|
+
file_paths = []
|
|
361
|
+
files.each do |f|
|
|
362
|
+
filepath = File.join(dir, f["filename"])
|
|
363
|
+
if !File.exist?(filepath)
|
|
364
|
+
print "Unable to find file name: #{f["filename"]} for source package: #{filepath}\n".color(:red)
|
|
365
|
+
abort("Aborting...".color(:red))
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
disk_size = File.stat(filepath).size
|
|
369
|
+
if disk_size != f["size"]
|
|
370
|
+
print "File #{f["filename"]} has size: #{disk_size}, expected: #{f["size"]}\n".color(:red)
|
|
371
|
+
abort("Aborting...".color(:red))
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
print "Found DSC package file #{f["filename"]} for upload\n"
|
|
375
|
+
|
|
376
|
+
file_paths << filepath
|
|
377
|
+
end
|
|
378
|
+
file_paths
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def get_valid(prompt)
|
|
382
|
+
selection = ""
|
|
383
|
+
times = 0
|
|
384
|
+
until yield(selection)
|
|
385
|
+
if times > 0
|
|
386
|
+
puts "#{selection} is not a valid selection."
|
|
387
|
+
end
|
|
388
|
+
print "#{prompt}: "
|
|
389
|
+
selection = ::Kernel.gets.chomp
|
|
390
|
+
times += 1
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
selection
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
module PackageCloud
|
|
2
|
+
module CLI
|
|
3
|
+
class GpgKey < Base
|
|
4
|
+
desc "list user/repository",
|
|
5
|
+
"list all GPG keys for specified repository"
|
|
6
|
+
def list(repo_name)
|
|
7
|
+
print "Looking for repository at #{repo_name}... "
|
|
8
|
+
repo = client.repository(repo_name)
|
|
9
|
+
print "success!\n"
|
|
10
|
+
|
|
11
|
+
keys = repo.gpg_keys
|
|
12
|
+
puts "GPG Keys for #{repo_name}:\n"
|
|
13
|
+
|
|
14
|
+
keys.each_with_index do |key, i|
|
|
15
|
+
if key.keytype == "package"
|
|
16
|
+
keytype = "Package signing key"
|
|
17
|
+
else
|
|
18
|
+
keytype = "Repository signing key"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
puts "Key name: #{key.name}"
|
|
22
|
+
puts "Key type: #{keytype}"
|
|
23
|
+
puts "Key fingerprint: #{key.fingerprint}"
|
|
24
|
+
puts "GPG key url: #{key.download_url}"
|
|
25
|
+
puts
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
desc "create user/repository /path/to/gpg-key",
|
|
30
|
+
"create a package signing GPG key using the specified file"
|
|
31
|
+
def create(repo_name, file_path)
|
|
32
|
+
print "Looking for repository at #{repo_name}... "
|
|
33
|
+
repo = client.repository(repo_name)
|
|
34
|
+
print "success!\n"
|
|
35
|
+
repo.create_gpg_key(file_path)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
desc "destroy user/repository keyname",
|
|
39
|
+
"destroy specified package signing GPG key"
|
|
40
|
+
def destroy(repo_name, keyname)
|
|
41
|
+
ARGV.clear
|
|
42
|
+
print "Looking for repository at #{repo_name}... "
|
|
43
|
+
repo = client.repository(repo_name)
|
|
44
|
+
print "success!\n"
|
|
45
|
+
|
|
46
|
+
key = repo.gpg_keys.detect do |key|
|
|
47
|
+
key.name == keyname
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
if key
|
|
51
|
+
msg = "\nAre you sure you want to delete the GPG key #{keyname}? (y/n)"
|
|
52
|
+
answer = get_valid(msg) do |s|
|
|
53
|
+
s == "y" || s == "n"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
if answer == "y"
|
|
57
|
+
print "Attempting to destroy GPG key named #{keyname}... "
|
|
58
|
+
begin
|
|
59
|
+
key.destroy
|
|
60
|
+
rescue RestClient::ResourceNotFound =>e
|
|
61
|
+
print "\nError, could not find key: #{keyname}. No GPG keys deleted.\n".color(:red)
|
|
62
|
+
print "Please note that you cannot delete repository signing keys.\n"
|
|
63
|
+
exit(1)
|
|
64
|
+
else
|
|
65
|
+
print "success!\n".color(:green)
|
|
66
|
+
end
|
|
67
|
+
else
|
|
68
|
+
puts "Aborting...".color(:red)
|
|
69
|
+
end
|
|
70
|
+
else
|
|
71
|
+
puts "Wasn't able to find a GPG key name: #{keyname}".color(:red)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
module PackageCloud
|
|
2
|
+
module CLI
|
|
3
|
+
class MasterToken < Base
|
|
4
|
+
desc "list user/repository",
|
|
5
|
+
"list master tokens for specified repository"
|
|
6
|
+
def list(repo_name)
|
|
7
|
+
print "Looking for repository at #{repo_name}... "
|
|
8
|
+
repo = client.repository(repo_name)
|
|
9
|
+
print "success!\n"
|
|
10
|
+
|
|
11
|
+
tokens = repo.master_tokens
|
|
12
|
+
puts "Tokens for #{repo_name}:"
|
|
13
|
+
puts ""
|
|
14
|
+
tokens.each_with_index do |token, i|
|
|
15
|
+
puts " #{token.name} (#{token.value})"
|
|
16
|
+
puts " read tokens:"
|
|
17
|
+
token.read_tokens.each do |read_token|
|
|
18
|
+
puts " { id: #{read_token.id}, name: #{read_token.name}, value: #{read_token.value} }"
|
|
19
|
+
puts
|
|
20
|
+
end
|
|
21
|
+
puts "" unless i == tokens.length - 1
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
desc "create user/repository token_name",
|
|
26
|
+
"create a master token for the specified repository"
|
|
27
|
+
def create(repo_name, token_name)
|
|
28
|
+
print "Looking for repository at #{repo_name}... "
|
|
29
|
+
repo = client.repository(repo_name)
|
|
30
|
+
print "success!\n"
|
|
31
|
+
|
|
32
|
+
print "Attempting to create token named #{token_name}... "
|
|
33
|
+
resp = repo.create_master_token(token_name)
|
|
34
|
+
print "success!\n".color(:green)
|
|
35
|
+
print "Master token #{resp['name']} with value #{resp['value']} created.\n"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
desc "destroy user/repository token_name",
|
|
39
|
+
"destroy a master token for the specified repository"
|
|
40
|
+
def destroy(repo_name, token_name)
|
|
41
|
+
ARGV.clear # otherwise gets explodes
|
|
42
|
+
|
|
43
|
+
if token_name == "default"
|
|
44
|
+
abort("You can't delete the default master_token.".color(:red))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
print "Looking for repository at #{repo_name}... "
|
|
48
|
+
repo = client.repository(repo_name)
|
|
49
|
+
print "success!\n"
|
|
50
|
+
|
|
51
|
+
token = repo.master_tokens.detect do |token|
|
|
52
|
+
token.name == token_name
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
if token
|
|
56
|
+
msg = "\nAre you sure you want to delete #{token_name}?"
|
|
57
|
+
msg << " #{token.read_tokens.length} read tokens will no longer work afterwards (y/n)" if token.read_tokens.length > 0
|
|
58
|
+
answer = get_valid(msg) do |s|
|
|
59
|
+
s == "y" || s == "n"
|
|
60
|
+
end
|
|
61
|
+
if answer == "y"
|
|
62
|
+
print "Attempting to destroy token named #{token_name}... "
|
|
63
|
+
token.destroy
|
|
64
|
+
print "success!\n".color(:green)
|
|
65
|
+
else
|
|
66
|
+
puts "Aborting...".color(:red)
|
|
67
|
+
end
|
|
68
|
+
else
|
|
69
|
+
puts "Wasn't able to find a token named #{token_name}.".color(:red)
|
|
70
|
+
exit(1)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module PackageCloud
|
|
2
|
+
module CLI
|
|
3
|
+
class ReadToken < Base
|
|
4
|
+
desc "destroy user/repository mastertoken/readtoken",
|
|
5
|
+
"revokes read token associated to a master token"
|
|
6
|
+
def destroy(repo_name, master_and_read_token)
|
|
7
|
+
print "Looking for repository at #{repo_name}... "
|
|
8
|
+
repo = client.repository(repo_name)
|
|
9
|
+
|
|
10
|
+
given_master_token, given_read_token = master_and_read_token.split("/")
|
|
11
|
+
|
|
12
|
+
if given_master_token.nil? || given_read_token.nil?
|
|
13
|
+
print "invalid master token and/or read token!\n".color(:red)
|
|
14
|
+
exit(127)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
master_token = repo.master_tokens.detect { |t| t.name == given_master_token }
|
|
18
|
+
|
|
19
|
+
if master_token.nil?
|
|
20
|
+
print "couldn't find master token named #{given_master_token}\n".color(:red)
|
|
21
|
+
exit(127)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
read_token = master_token.read_tokens.detect { |t| t.name == given_read_token }
|
|
25
|
+
|
|
26
|
+
if read_token.nil?
|
|
27
|
+
print "couldn't find read token named #{given_read_token} for #{given_master_token}\n".color(:red)
|
|
28
|
+
exit(127)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
master_token_path = master_token.paths["self"]
|
|
32
|
+
|
|
33
|
+
read_token.destroy(master_token_path, read_token.id)
|
|
34
|
+
|
|
35
|
+
print "success!\n"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module PackageCloud
|
|
2
|
+
module CLI
|
|
3
|
+
class Repository < Base
|
|
4
|
+
option :private
|
|
5
|
+
desc "create repo_name",
|
|
6
|
+
"create repository called repo_name"
|
|
7
|
+
def create(repo_name)
|
|
8
|
+
config.read_or_create
|
|
9
|
+
client = Client.new(config)
|
|
10
|
+
|
|
11
|
+
print "Looking for repository at #{repo_name}... "
|
|
12
|
+
repo = client.create_repo(repo_name, options[:private])
|
|
13
|
+
print "success!\n".color(:green)
|
|
14
|
+
puts "Your repository has been created at:"
|
|
15
|
+
puts " #{repo["url"]}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
desc "list",
|
|
19
|
+
"list your repositories"
|
|
20
|
+
def list
|
|
21
|
+
repos = client.repositories
|
|
22
|
+
if repos.length == 0
|
|
23
|
+
puts "You have no repositories at the moment. Create one!"
|
|
24
|
+
else
|
|
25
|
+
puts "Your repositories:"
|
|
26
|
+
puts ""
|
|
27
|
+
repos.each_with_index do |repo, i|
|
|
28
|
+
puts " #{repo.fqname} (#{repo.private_human})"
|
|
29
|
+
puts " last push: #{repo.last_push_human} | packages: #{repo.package_count_human}"
|
|
30
|
+
puts "" unless i == repos.length - 1
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
desc "install user/repo package_type",
|
|
36
|
+
"install user/repo for package_type"
|
|
37
|
+
def install(repo, package_type)
|
|
38
|
+
if Process.uid != 0 && package_type != "gem"
|
|
39
|
+
abort("You have to run install as root.".color(:red))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
print "Locating repository at #{repo}... "
|
|
43
|
+
repo = client.repository(repo)
|
|
44
|
+
print "success!\n"
|
|
45
|
+
script = repo.install_script(package_type)
|
|
46
|
+
pid = fork { exec("/bin/bash", script.path) }
|
|
47
|
+
Process.waitpid(pid)
|
|
48
|
+
script.unlink
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|