cloudsmith-cli 1.20.0__py3-none-any.whl

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.
Files changed (130) hide show
  1. cloudsmith_cli/__init__.py +10 -0
  2. cloudsmith_cli/__main__.py +8 -0
  3. cloudsmith_cli/cli/__init__.py +1 -0
  4. cloudsmith_cli/cli/command.py +160 -0
  5. cloudsmith_cli/cli/commands/__init__.py +33 -0
  6. cloudsmith_cli/cli/commands/auth.py +173 -0
  7. cloudsmith_cli/cli/commands/check.py +129 -0
  8. cloudsmith_cli/cli/commands/copy.py +98 -0
  9. cloudsmith_cli/cli/commands/credential_helper/__init__.py +39 -0
  10. cloudsmith_cli/cli/commands/credential_helper/docker.py +66 -0
  11. cloudsmith_cli/cli/commands/credential_helper/manage.py +299 -0
  12. cloudsmith_cli/cli/commands/delete.py +67 -0
  13. cloudsmith_cli/cli/commands/dependencies.py +108 -0
  14. cloudsmith_cli/cli/commands/docs.py +16 -0
  15. cloudsmith_cli/cli/commands/download.py +620 -0
  16. cloudsmith_cli/cli/commands/entitlements.py +819 -0
  17. cloudsmith_cli/cli/commands/help_.py +12 -0
  18. cloudsmith_cli/cli/commands/list_.py +317 -0
  19. cloudsmith_cli/cli/commands/login.py +99 -0
  20. cloudsmith_cli/cli/commands/logout.py +151 -0
  21. cloudsmith_cli/cli/commands/main.py +73 -0
  22. cloudsmith_cli/cli/commands/mcp.py +523 -0
  23. cloudsmith_cli/cli/commands/metadata.py +503 -0
  24. cloudsmith_cli/cli/commands/metrics/__init__.py +2 -0
  25. cloudsmith_cli/cli/commands/metrics/command.py +20 -0
  26. cloudsmith_cli/cli/commands/metrics/entitlements.py +148 -0
  27. cloudsmith_cli/cli/commands/metrics/packages.py +134 -0
  28. cloudsmith_cli/cli/commands/move.py +111 -0
  29. cloudsmith_cli/cli/commands/policy/__init__.py +3 -0
  30. cloudsmith_cli/cli/commands/policy/command.py +20 -0
  31. cloudsmith_cli/cli/commands/policy/deny.py +248 -0
  32. cloudsmith_cli/cli/commands/policy/license.py +335 -0
  33. cloudsmith_cli/cli/commands/policy/vulnerability.py +322 -0
  34. cloudsmith_cli/cli/commands/push.py +1323 -0
  35. cloudsmith_cli/cli/commands/quarantine.py +148 -0
  36. cloudsmith_cli/cli/commands/quota/__init__.py +2 -0
  37. cloudsmith_cli/cli/commands/quota/command.py +20 -0
  38. cloudsmith_cli/cli/commands/quota/history.py +122 -0
  39. cloudsmith_cli/cli/commands/quota/quota.py +107 -0
  40. cloudsmith_cli/cli/commands/repos.py +330 -0
  41. cloudsmith_cli/cli/commands/resync.py +90 -0
  42. cloudsmith_cli/cli/commands/status.py +92 -0
  43. cloudsmith_cli/cli/commands/tags.py +375 -0
  44. cloudsmith_cli/cli/commands/tokens.py +318 -0
  45. cloudsmith_cli/cli/commands/upstream.py +479 -0
  46. cloudsmith_cli/cli/commands/vulnerabilities.py +141 -0
  47. cloudsmith_cli/cli/commands/whoami.py +188 -0
  48. cloudsmith_cli/cli/config.py +635 -0
  49. cloudsmith_cli/cli/decorators.py +624 -0
  50. cloudsmith_cli/cli/exceptions.py +215 -0
  51. cloudsmith_cli/cli/metadata_common.py +146 -0
  52. cloudsmith_cli/cli/saml.py +109 -0
  53. cloudsmith_cli/cli/table.py +59 -0
  54. cloudsmith_cli/cli/types.py +14 -0
  55. cloudsmith_cli/cli/utils.py +267 -0
  56. cloudsmith_cli/cli/validators.py +378 -0
  57. cloudsmith_cli/cli/webserver.py +263 -0
  58. cloudsmith_cli/core/__init__.py +1 -0
  59. cloudsmith_cli/core/api/__init__.py +1 -0
  60. cloudsmith_cli/core/api/distros.py +31 -0
  61. cloudsmith_cli/core/api/entitlements.py +130 -0
  62. cloudsmith_cli/core/api/exceptions.py +57 -0
  63. cloudsmith_cli/core/api/files.py +131 -0
  64. cloudsmith_cli/core/api/init.py +109 -0
  65. cloudsmith_cli/core/api/metadata.py +217 -0
  66. cloudsmith_cli/core/api/metrics.py +78 -0
  67. cloudsmith_cli/core/api/orgs.py +201 -0
  68. cloudsmith_cli/core/api/packages.py +309 -0
  69. cloudsmith_cli/core/api/quota.py +64 -0
  70. cloudsmith_cli/core/api/rates.py +28 -0
  71. cloudsmith_cli/core/api/repos.py +81 -0
  72. cloudsmith_cli/core/api/status.py +27 -0
  73. cloudsmith_cli/core/api/upstreams.py +72 -0
  74. cloudsmith_cli/core/api/user.py +109 -0
  75. cloudsmith_cli/core/api/version.py +15 -0
  76. cloudsmith_cli/core/api/vulnerabilities.py +230 -0
  77. cloudsmith_cli/core/cache_utils.py +160 -0
  78. cloudsmith_cli/core/config.py +140 -0
  79. cloudsmith_cli/core/credentials/__init__.py +0 -0
  80. cloudsmith_cli/core/credentials/chain.py +69 -0
  81. cloudsmith_cli/core/credentials/models.py +44 -0
  82. cloudsmith_cli/core/credentials/oidc/__init__.py +6 -0
  83. cloudsmith_cli/core/credentials/oidc/cache.py +220 -0
  84. cloudsmith_cli/core/credentials/oidc/detectors/__init__.py +122 -0
  85. cloudsmith_cli/core/credentials/oidc/detectors/aws.py +85 -0
  86. cloudsmith_cli/core/credentials/oidc/detectors/azure_devops.py +70 -0
  87. cloudsmith_cli/core/credentials/oidc/detectors/base.py +26 -0
  88. cloudsmith_cli/core/credentials/oidc/detectors/bitbucket_pipelines.py +35 -0
  89. cloudsmith_cli/core/credentials/oidc/detectors/circleci.py +40 -0
  90. cloudsmith_cli/core/credentials/oidc/detectors/generic.py +42 -0
  91. cloudsmith_cli/core/credentials/oidc/detectors/github_actions.py +64 -0
  92. cloudsmith_cli/core/credentials/oidc/detectors/gitlab_ci.py +49 -0
  93. cloudsmith_cli/core/credentials/oidc/exchange.py +87 -0
  94. cloudsmith_cli/core/credentials/provider.py +17 -0
  95. cloudsmith_cli/core/credentials/providers/__init__.py +15 -0
  96. cloudsmith_cli/core/credentials/providers/cli_flag.py +24 -0
  97. cloudsmith_cli/core/credentials/providers/credentials_file.py +24 -0
  98. cloudsmith_cli/core/credentials/providers/env_var.py +24 -0
  99. cloudsmith_cli/core/credentials/providers/keyring_provider.py +59 -0
  100. cloudsmith_cli/core/credentials/providers/oidc_provider.py +115 -0
  101. cloudsmith_cli/core/download.py +594 -0
  102. cloudsmith_cli/core/keyring.py +171 -0
  103. cloudsmith_cli/core/mcp/__init__.py +0 -0
  104. cloudsmith_cli/core/mcp/data.py +17 -0
  105. cloudsmith_cli/core/mcp/server.py +786 -0
  106. cloudsmith_cli/core/pagination.py +131 -0
  107. cloudsmith_cli/core/ratelimits.py +88 -0
  108. cloudsmith_cli/core/rest.py +255 -0
  109. cloudsmith_cli/core/utils.py +95 -0
  110. cloudsmith_cli/core/version.py +20 -0
  111. cloudsmith_cli/credential_helpers/__init__.py +7 -0
  112. cloudsmith_cli/credential_helpers/backends.py +41 -0
  113. cloudsmith_cli/credential_helpers/common.py +111 -0
  114. cloudsmith_cli/credential_helpers/custom_domains.py +281 -0
  115. cloudsmith_cli/credential_helpers/docker/__init__.py +4 -0
  116. cloudsmith_cli/credential_helpers/docker/installer.py +349 -0
  117. cloudsmith_cli/credential_helpers/docker/runtime.py +117 -0
  118. cloudsmith_cli/credential_helpers/launchers.py +175 -0
  119. cloudsmith_cli/data/VERSION +1 -0
  120. cloudsmith_cli/data/config.ini +23 -0
  121. cloudsmith_cli/data/credentials.ini +14 -0
  122. cloudsmith_cli/templates/__init__.py +3 -0
  123. cloudsmith_cli/templates/auth_error.html +45 -0
  124. cloudsmith_cli/templates/auth_success.html +37 -0
  125. cloudsmith_cli-1.20.0.dist-info/METADATA +610 -0
  126. cloudsmith_cli-1.20.0.dist-info/RECORD +130 -0
  127. cloudsmith_cli-1.20.0.dist-info/WHEEL +5 -0
  128. cloudsmith_cli-1.20.0.dist-info/entry_points.txt +2 -0
  129. cloudsmith_cli-1.20.0.dist-info/licenses/LICENSE +201 -0
  130. cloudsmith_cli-1.20.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,503 @@
1
+ """CLI/Commands - Manage metadata attached to packages."""
2
+
3
+ import json
4
+
5
+ import click
6
+
7
+ from ...core.api.metadata import (
8
+ create_metadata as api_create_metadata,
9
+ delete_metadata as api_delete_metadata,
10
+ get_metadata as api_get_metadata,
11
+ list_metadata as api_list_metadata,
12
+ update_metadata as api_update_metadata,
13
+ )
14
+ from ...core.api.packages import get_package_slug_perm as api_get_package_slug_perm
15
+ from ...core.pagination import paginate_results
16
+ from .. import command, decorators, utils, validators
17
+ from ..exceptions import handle_api_exceptions
18
+ from ..metadata_common import (
19
+ attach_metadata_options,
20
+ require_metadata_content_type,
21
+ resolve_metadata_content,
22
+ )
23
+ from ..utils import maybe_spinner
24
+ from .main import main
25
+
26
+ _METADATA_HEADERS = [
27
+ "Slug",
28
+ "Content type",
29
+ "Classification",
30
+ "Source kind",
31
+ "Source identity",
32
+ ]
33
+
34
+
35
+ def _format_metadata_row(entry):
36
+ return [
37
+ click.style(entry.get("slug_perm") or "", fg="cyan"),
38
+ click.style(entry.get("content_type") or "", fg="yellow"),
39
+ click.style(str(entry.get("classification", "")), fg="magenta"),
40
+ click.style(str(entry.get("source_kind", "")), fg="blue"),
41
+ click.style(entry.get("source_identity") or "", fg="green"),
42
+ ]
43
+
44
+
45
+ def _echo_action(message, use_stderr):
46
+ """Print an in-progress status message."""
47
+ click.echo(message, nl=False, err=use_stderr)
48
+
49
+
50
+ def _print_metadata_table(opts, entries, page_info=None, page_all=False):
51
+ """Print a list of metadata entries as a table or JSON."""
52
+ if utils.maybe_print_as_json(
53
+ opts, list(entries), page_info=None if page_all else page_info
54
+ ):
55
+ return
56
+
57
+ rows = [
58
+ _format_metadata_row(e)
59
+ for e in sorted(entries, key=lambda e: e.get("slug_perm") or "")
60
+ ]
61
+
62
+ if rows:
63
+ click.echo()
64
+ utils.pretty_print_table(_METADATA_HEADERS, rows)
65
+
66
+ click.echo()
67
+
68
+ num_results = len(rows)
69
+ list_suffix = f"metadata entr{'ies' if num_results != 1 else 'y'}"
70
+ utils.pretty_print_list_info(
71
+ num_results=num_results,
72
+ page_info=None if page_all else page_info,
73
+ suffix=f"{list_suffix} retrieved" if page_all else f"{list_suffix} visible",
74
+ page_all=page_all,
75
+ )
76
+
77
+
78
+ def _print_metadata_entry(opts, entry):
79
+ """Print a single metadata entry as a table + indented JSON content."""
80
+ if utils.maybe_print_as_json(opts, entry):
81
+ return
82
+
83
+ click.echo()
84
+ utils.pretty_print_table(_METADATA_HEADERS, [_format_metadata_row(entry)])
85
+ click.echo()
86
+
87
+ content = entry.get("content")
88
+ if content is not None:
89
+ click.secho("Content:", bold=True)
90
+ click.echo(json.dumps(content, indent=2, sort_keys=True))
91
+
92
+
93
+ @main.group(name="metadata", cls=command.AliasGroup)
94
+ @decorators.common_cli_config_options
95
+ @decorators.common_cli_output_options
96
+ @decorators.common_api_auth_options
97
+ @decorators.initialise_api
98
+ @click.pass_context
99
+ def metadata_(ctx, opts): # pylint: disable=unused-argument
100
+ """
101
+ Manage package metadata.
102
+ """
103
+
104
+
105
+ @metadata_.command(name="list", aliases=["ls"])
106
+ @decorators.common_cli_config_options
107
+ @decorators.common_cli_output_options
108
+ @decorators.common_cli_list_options
109
+ @decorators.common_api_auth_options
110
+ @decorators.initialise_api
111
+ @click.argument(
112
+ "owner_repo_package",
113
+ metavar="OWNER/REPO/PACKAGE",
114
+ callback=validators.validate_owner_repo_package,
115
+ )
116
+ @click.argument("metadata_slug_perm", required=False, default=None)
117
+ @click.option(
118
+ "--source-kind",
119
+ "source_kind",
120
+ default=None,
121
+ help=(
122
+ "Filter by source kind name (one of: unknown, system, upstream, "
123
+ "custom, third_party). Ignored when METADATA_SLUG_PERM is given."
124
+ ),
125
+ )
126
+ @click.option(
127
+ "--classification",
128
+ "classification",
129
+ default=None,
130
+ help=(
131
+ "Filter by classification name (one of: unknown, intrinsic, security, "
132
+ "provenance, sbom, generic). Ignored when METADATA_SLUG_PERM is given."
133
+ ),
134
+ )
135
+ @click.pass_context
136
+ def list_metadata(
137
+ ctx,
138
+ opts,
139
+ owner_repo_package,
140
+ metadata_slug_perm,
141
+ page,
142
+ page_size,
143
+ page_all,
144
+ source_kind,
145
+ classification,
146
+ ):
147
+ """
148
+ List package metadata.
149
+
150
+ OWNER/REPO/PACKAGE: target package.
151
+
152
+ METADATA_SLUG_PERM (optional): fetch one metadata entry. Pagination and
153
+ filters are ignored.
154
+
155
+ \b
156
+ Examples:
157
+ $ cloudsmith metadata list your-org/your-repo/your-pkg
158
+ $ cloudsmith metadata list your-org/your-repo/your-pkg --classification provenance
159
+ $ cloudsmith metadata list your-org/your-repo/your-pkg meta-slug-perm
160
+ """
161
+ owner, repo, package = owner_repo_package
162
+ use_stderr = utils.should_use_stderr(opts)
163
+
164
+ if metadata_slug_perm:
165
+ _echo_action(
166
+ "Fetching metadata %(metadata)s for %(package)s ... "
167
+ % {
168
+ "metadata": click.style(metadata_slug_perm, bold=True),
169
+ "package": click.style(package, bold=True),
170
+ },
171
+ use_stderr,
172
+ )
173
+
174
+ context_msg = "Could not fetch package metadata."
175
+ with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
176
+ with maybe_spinner(opts):
177
+ slug_perm = api_get_package_slug_perm(
178
+ owner=owner, repo=repo, identifier=package
179
+ )
180
+ entry = api_get_metadata(slug_perm, metadata_slug_perm)
181
+
182
+ click.secho("OK", fg="green", err=use_stderr)
183
+ _print_metadata_entry(opts, entry)
184
+ return
185
+
186
+ _echo_action(
187
+ "Listing metadata for %(package)s ... "
188
+ % {"package": click.style(package, bold=True)},
189
+ use_stderr,
190
+ )
191
+
192
+ context_msg = "Could not list package metadata."
193
+ with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
194
+ with maybe_spinner(opts):
195
+ slug_perm = api_get_package_slug_perm(
196
+ owner=owner, repo=repo, identifier=package
197
+ )
198
+ entries, page_info = paginate_results(
199
+ api_list_metadata,
200
+ page_all=page_all,
201
+ page=page,
202
+ page_size=page_size,
203
+ package_slug_perm=slug_perm,
204
+ source_kind=source_kind,
205
+ classification=classification,
206
+ )
207
+
208
+ click.secho("OK", fg="green", err=use_stderr)
209
+ _print_metadata_table(opts, entries, page_info=page_info, page_all=page_all)
210
+
211
+
212
+ @metadata_.command(name="add")
213
+ @decorators.common_cli_config_options
214
+ @decorators.common_cli_output_options
215
+ @decorators.common_api_auth_options
216
+ @decorators.initialise_api
217
+ @click.argument(
218
+ "owner_repo_package",
219
+ metavar="OWNER/REPO/PACKAGE",
220
+ callback=validators.validate_owner_repo_package,
221
+ )
222
+ @click.option(
223
+ "--content-type",
224
+ "content_type",
225
+ required=True,
226
+ help=(
227
+ "Content type for metadata content (for example, 'application/json'). "
228
+ "Content type is immutable after creation."
229
+ ),
230
+ )
231
+ @click.option(
232
+ "--file",
233
+ "content_file",
234
+ type=click.Path(
235
+ exists=True,
236
+ dir_okay=False,
237
+ readable=True,
238
+ resolve_path=True,
239
+ allow_dash=True,
240
+ ),
241
+ default=None,
242
+ help="Read metadata content from a JSON file. Use '-' for stdin.",
243
+ )
244
+ @click.option(
245
+ "--content",
246
+ "inline_content",
247
+ default=None,
248
+ help="Set metadata content from inline JSON. Cannot be used with --file.",
249
+ )
250
+ @click.option(
251
+ "--source-identity",
252
+ "source_identity",
253
+ default=None,
254
+ help=(
255
+ "Identifier for the metadata source. " "Defaults to 'cloudsmith-cli@<version>'."
256
+ ),
257
+ )
258
+ @click.pass_context
259
+ def add_metadata(
260
+ ctx,
261
+ opts,
262
+ owner_repo_package,
263
+ content_type,
264
+ content_file,
265
+ inline_content,
266
+ source_identity,
267
+ ):
268
+ """
269
+ Attach metadata to a package.
270
+
271
+ OWNER/REPO/PACKAGE: target package.
272
+
273
+ Exactly one of --file or --content must be provided.
274
+ Content type is set on creation and cannot be changed.
275
+
276
+ \b
277
+ Examples:
278
+ $ cloudsmith metadata add your-org/your-repo/your-pkg \\
279
+ --content-type application/json \\
280
+ --content '{"foo": "bar"}'
281
+ $ cat metadata.json | cloudsmith metadata add your-org/your-repo/your-pkg \\
282
+ --content-type application/json \\
283
+ --file -
284
+ $ cloudsmith metadata add your-org/your-repo/your-pkg \\
285
+ --content-type application/vnd.jfrog.buildinfo+json \\
286
+ --file buildinfo.json
287
+ """
288
+ owner, repo, package = owner_repo_package
289
+ use_stderr = utils.should_use_stderr(opts)
290
+
291
+ metadata = resolve_metadata_content(
292
+ content_file=content_file,
293
+ inline_content=inline_content,
294
+ required=True,
295
+ file_option_name="--file",
296
+ content_option_name="--content",
297
+ )
298
+ require_metadata_content_type(
299
+ content_type=content_type,
300
+ content_provided=metadata.provided,
301
+ option_name="--content-type",
302
+ )
303
+ metadata = attach_metadata_options(
304
+ metadata,
305
+ content_type=content_type,
306
+ source_identity=source_identity,
307
+ )
308
+
309
+ _echo_action(
310
+ "Attaching metadata to %(package)s ... "
311
+ % {"package": click.style(package, bold=True)},
312
+ use_stderr,
313
+ )
314
+
315
+ context_msg = "Could not attach metadata."
316
+ with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
317
+ with maybe_spinner(opts):
318
+ slug_perm = api_get_package_slug_perm(
319
+ owner=owner, repo=repo, identifier=package
320
+ )
321
+ entry = api_create_metadata(
322
+ slug_perm,
323
+ content=metadata.content,
324
+ content_type=metadata.content_type,
325
+ source_identity=metadata.source_identity,
326
+ )
327
+
328
+ click.secho("OK", fg="green", err=use_stderr)
329
+ _print_metadata_entry(opts, entry)
330
+
331
+
332
+ @metadata_.command(name="update")
333
+ @decorators.common_cli_config_options
334
+ @decorators.common_cli_output_options
335
+ @decorators.common_api_auth_options
336
+ @decorators.initialise_api
337
+ @click.argument(
338
+ "owner_repo_package",
339
+ metavar="OWNER/REPO/PACKAGE",
340
+ callback=validators.validate_owner_repo_package,
341
+ )
342
+ @click.argument("metadata_slug_perm")
343
+ @click.option(
344
+ "--file",
345
+ "content_file",
346
+ type=click.Path(
347
+ exists=True,
348
+ dir_okay=False,
349
+ readable=True,
350
+ resolve_path=True,
351
+ allow_dash=True,
352
+ ),
353
+ default=None,
354
+ help="Read replacement metadata content from a JSON file. Use '-' for stdin.",
355
+ )
356
+ @click.option(
357
+ "--content",
358
+ "inline_content",
359
+ default=None,
360
+ help=(
361
+ "Set replacement metadata content from inline JSON. Cannot be used with "
362
+ "--file."
363
+ ),
364
+ )
365
+ @click.option(
366
+ "--source-identity",
367
+ "source_identity",
368
+ default=None,
369
+ help="Update the metadata source identity.",
370
+ )
371
+ @click.pass_context
372
+ def update_metadata(
373
+ ctx,
374
+ opts,
375
+ owner_repo_package,
376
+ metadata_slug_perm,
377
+ content_file,
378
+ inline_content,
379
+ source_identity,
380
+ ):
381
+ """
382
+ Update package metadata.
383
+
384
+ OWNER/REPO/PACKAGE: target package.
385
+ METADATA_SLUG_PERM: permanent slug for the metadata entry.
386
+
387
+ Content type cannot be changed after creation.
388
+
389
+ \b
390
+ Examples:
391
+ $ cloudsmith metadata update your-org/your-repo/your-pkg meta-slug \\
392
+ --content '{"foo": "baz"}'
393
+ $ cat metadata.json | cloudsmith metadata update your-org/your-repo/your-pkg meta-slug \\
394
+ --file -
395
+ """
396
+ owner, repo, package = owner_repo_package
397
+ use_stderr = utils.should_use_stderr(opts)
398
+
399
+ metadata = resolve_metadata_content(
400
+ content_file=content_file,
401
+ inline_content=inline_content,
402
+ required=False,
403
+ file_option_name="--file",
404
+ content_option_name="--content",
405
+ )
406
+
407
+ patch_kwargs = {}
408
+ if metadata.provided:
409
+ patch_kwargs["content"] = metadata.content
410
+ if source_identity is not None:
411
+ patch_kwargs["source_identity"] = source_identity
412
+ if not patch_kwargs:
413
+ raise click.UsageError(
414
+ "Nothing to update. Provide --file, --content, or --source-identity."
415
+ )
416
+
417
+ _echo_action(
418
+ "Updating metadata %(metadata)s for %(package)s ... "
419
+ % {
420
+ "metadata": click.style(metadata_slug_perm, bold=True),
421
+ "package": click.style(package, bold=True),
422
+ },
423
+ use_stderr,
424
+ )
425
+
426
+ context_msg = "Could not update package metadata."
427
+ with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
428
+ with maybe_spinner(opts):
429
+ slug_perm = api_get_package_slug_perm(
430
+ owner=owner, repo=repo, identifier=package
431
+ )
432
+ entry = api_update_metadata(slug_perm, metadata_slug_perm, **patch_kwargs)
433
+
434
+ click.secho("OK", fg="green", err=use_stderr)
435
+ _print_metadata_entry(opts, entry)
436
+
437
+
438
+ @metadata_.command(name="remove", aliases=["rm"])
439
+ @decorators.common_cli_config_options
440
+ @decorators.common_cli_output_options
441
+ @decorators.common_api_auth_options
442
+ @decorators.initialise_api
443
+ @click.argument(
444
+ "owner_repo_package",
445
+ metavar="OWNER/REPO/PACKAGE",
446
+ callback=validators.validate_owner_repo_package,
447
+ )
448
+ @click.argument("metadata_slug_perm")
449
+ @click.option(
450
+ "-y",
451
+ "--yes",
452
+ default=False,
453
+ is_flag=True,
454
+ help="Skip confirmation prompt.",
455
+ )
456
+ @click.pass_context
457
+ def remove_metadata(ctx, opts, owner_repo_package, metadata_slug_perm, yes):
458
+ """
459
+ Remove package metadata.
460
+
461
+ OWNER/REPO/PACKAGE: target package.
462
+ METADATA_SLUG_PERM: permanent slug for the metadata entry.
463
+
464
+ \b
465
+ Example:
466
+ $ cloudsmith metadata remove your-org/your-repo/your-pkg meta-slug
467
+ """
468
+ owner, repo, package = owner_repo_package
469
+ use_stderr = utils.should_use_stderr(opts)
470
+
471
+ remove_args = {
472
+ "metadata": click.style(metadata_slug_perm, bold=True),
473
+ "package": click.style(package, bold=True),
474
+ }
475
+
476
+ prompt = "remove metadata %(metadata)s from package %(package)s" % remove_args
477
+ if not utils.confirm_operation(prompt, assume_yes=yes, err=use_stderr):
478
+ return
479
+
480
+ _echo_action(
481
+ "Removing metadata %(metadata)s from %(package)s ... " % remove_args,
482
+ use_stderr,
483
+ )
484
+
485
+ context_msg = "Could not remove package metadata."
486
+ with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
487
+ with maybe_spinner(opts):
488
+ slug_perm = api_get_package_slug_perm(
489
+ owner=owner, repo=repo, identifier=package
490
+ )
491
+ api_delete_metadata(slug_perm, metadata_slug_perm)
492
+
493
+ click.secho("OK", fg="green", err=use_stderr)
494
+
495
+ result_payload = {"deleted": True, "slug_perm": metadata_slug_perm}
496
+ if utils.maybe_print_as_json(opts, result_payload):
497
+ return
498
+
499
+ click.echo()
500
+ click.secho(
501
+ "Metadata removed: %(slug)s."
502
+ % {"slug": click.style(metadata_slug_perm, bold=True)}
503
+ )
@@ -0,0 +1,2 @@
1
+ from .entitlements import usage as entitlement_usage
2
+ from .packages import usage as package_usage
@@ -0,0 +1,20 @@
1
+ """CLI/Commands - Import all metric commands."""
2
+
3
+ import click
4
+
5
+ from ... import command, decorators
6
+ from ..main import main
7
+
8
+
9
+ @main.group(cls=command.AliasGroup, name="metrics", aliases=[])
10
+ @decorators.common_cli_config_options
11
+ @decorators.common_cli_output_options
12
+ @decorators.common_api_auth_options
13
+ @decorators.initialise_api
14
+ @click.pass_context
15
+ def metrics(ctx, opts): # pylink: disable=unused-argument
16
+ """
17
+ Retrieve Metrics.
18
+
19
+ See the help for subcommands for more information on each.
20
+ """
@@ -0,0 +1,148 @@
1
+ """CLI/Commands - retrieve metrics."""
2
+
3
+ import click
4
+
5
+ from ....core.api import metrics as api
6
+ from ... import decorators, utils, validators
7
+ from ...exceptions import handle_api_exceptions
8
+ from ...utils import maybe_spinner
9
+ from .command import metrics
10
+
11
+
12
+ def _print_activity_table(opts, data):
13
+ """Print token activity as a table."""
14
+ utils.pretty_print_table(
15
+ headers=["Active", "Inactive", "Total"],
16
+ rows=[
17
+ [
18
+ click.style(str(getattr(data, k, 0)), fg="green")
19
+ for k in ("active", "inactive", "total")
20
+ ]
21
+ ],
22
+ title="Activity Summary (Active = Has Downloads)",
23
+ )
24
+
25
+ click.echo()
26
+
27
+
28
+ def _print_metrics_table(opts, data):
29
+ """Print metrics as a table."""
30
+ category_keys = {"Bandwidth": "bandwidth", "Downloads": "downloads"}
31
+
32
+ metrics_keys = {
33
+ "Average": "average",
34
+ "Lowest": "lowest",
35
+ "Highest": "highest",
36
+ "Total": "total",
37
+ }
38
+
39
+ headers = ["Metric"]
40
+ headers.extend(metrics_keys.keys())
41
+ rows = []
42
+
43
+ for category_header, category_key in category_keys.items():
44
+ category_data = getattr(data, category_key)
45
+ cols = [category_header]
46
+ for metric_key in metrics_keys.values():
47
+ metric_data = getattr(category_data, metric_key, {})
48
+ if hasattr(metric_data, "display"):
49
+ value = getattr(metric_data, "display")
50
+ else:
51
+ value = getattr(metric_data, "value")
52
+ value = str(value or 0)
53
+ cols.append(click.style(value, fg="green"))
54
+ rows.append(cols)
55
+
56
+ utils.pretty_print_table(headers=headers, rows=rows, title="Entitlement Metrics")
57
+
58
+ click.echo()
59
+
60
+
61
+ @metrics.command(name="entitlements", aliases=["ents", "tokens"])
62
+ @decorators.common_cli_config_options
63
+ @decorators.common_cli_output_options
64
+ @decorators.common_api_auth_options
65
+ @decorators.initialise_api
66
+ @click.argument(
67
+ "owner_repo",
68
+ metavar="OWNER or OWNER/REPO",
69
+ callback=validators.validate_required_owner_optional_repo,
70
+ default="",
71
+ required=True,
72
+ )
73
+ @click.option(
74
+ "--tokens",
75
+ type=str,
76
+ required=False,
77
+ help=(
78
+ "A comma separated list of entitlement token identifiers (i.e. slug_perm) or "
79
+ "token secrets. If a list is not specified then all entitlement tokens will "
80
+ "be included for a given namespace or repository."
81
+ ),
82
+ )
83
+ @click.option(
84
+ "--start",
85
+ type=str,
86
+ required=False,
87
+ help=(
88
+ "Include metrics from and including this UTC date or UTC datetime. "
89
+ "For example '2020-12-31' or '2021-12-13T00:00:00Z'."
90
+ ),
91
+ )
92
+ @click.option(
93
+ "--finish",
94
+ type=str,
95
+ required=False,
96
+ help=(
97
+ "Include metrics upto and including this UTC date or UTC datetime. "
98
+ "For example '2020-12-31' or '2021-12-13T00:00:00Z'."
99
+ ),
100
+ )
101
+ @click.pass_context
102
+ def usage(ctx, opts, owner_repo, tokens, start, finish):
103
+ """
104
+ Retrieve token usage metrics.
105
+
106
+ OWNER/REPO: Specify the OWNER namespace (i.e user or org) and repository to retrieve the
107
+ metrics for that namespace/repository combination.
108
+
109
+ If REPO isn't specified, all repositories will be included from the
110
+ OWNER namespace.
111
+ """
112
+
113
+ use_stderr = utils.should_use_stderr(opts)
114
+
115
+ click.echo("Getting usage metrics ... ", nl=False, err=use_stderr)
116
+
117
+ owner = None
118
+ repo = None
119
+
120
+ if isinstance(owner_repo, list) and len(owner_repo) == 2:
121
+ owner, repo = owner_repo
122
+ elif len(owner_repo) == 1:
123
+ owner = owner_repo[0]
124
+ repo = None
125
+
126
+ data = None
127
+ context_msg = "Failed to get list of metrics!"
128
+ data = {}
129
+ with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
130
+ with maybe_spinner(opts):
131
+ if owner and repo:
132
+ data = api.get_repository_entitlements_metrics(
133
+ owner=owner, repo=repo, tokens=tokens, start=start, finish=finish
134
+ )
135
+ elif owner:
136
+ data = api.get_namespace_entitlements_metrics(
137
+ owner=owner, repo=repo, tokens=tokens, start=start, finish=finish
138
+ )
139
+
140
+ click.secho("OK", fg="green", err=use_stderr)
141
+
142
+ if utils.maybe_print_as_json(opts, data):
143
+ return
144
+
145
+ click.echo()
146
+
147
+ _print_activity_table(opts, data)
148
+ _print_metrics_table(opts, data)