cloudsmith-cli 1.23.0__tar.gz → 1.24.0__tar.gz
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.
- {cloudsmith_cli-1.23.0/cloudsmith_cli.egg-info → cloudsmith_cli-1.24.0}/PKG-INFO +17 -3
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/README.md +15 -1
- cloudsmith_cli-1.24.0/cloudsmith_cli/__main__.py +15 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/auth.py +41 -4
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/push.py +77 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/decorators.py +24 -1
- cloudsmith_cli-1.24.0/cloudsmith_cli/cli/dsc_parser.py +268 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/exceptions.py +3 -2
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/credentials/oidc/detectors/aws.py +33 -1
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/keyring.py +31 -0
- cloudsmith_cli-1.24.0/cloudsmith_cli/data/VERSION +1 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0/cloudsmith_cli.egg-info}/PKG-INFO +17 -3
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli.egg-info/SOURCES.txt +1 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli.egg-info/requires.txt +1 -1
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/pyproject.toml +2 -1
- cloudsmith_cli-1.23.0/cloudsmith_cli/__main__.py +0 -8
- cloudsmith_cli-1.23.0/cloudsmith_cli/data/VERSION +0 -1
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/LICENSE +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/MANIFEST.in +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/__init__.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/__init__.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/command.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/__init__.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/check.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/copy.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/credential_helper/__init__.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/credential_helper/docker.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/credential_helper/generic.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/credential_helper/manage.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/delete.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/dependencies.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/docs.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/domains.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/download.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/entitlements.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/help_.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/list_.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/login.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/logout.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/main.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/mcp.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/metadata.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/metrics/__init__.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/metrics/command.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/metrics/entitlements.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/metrics/packages.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/move.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/policy/__init__.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/policy/command.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/policy/deny.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/policy/license.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/policy/vulnerability.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/quarantine.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/quota/__init__.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/quota/command.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/quota/history.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/quota/quota.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/repos.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/resync.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/status.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/tags.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/tokens.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/upstream.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/vulnerabilities.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/commands/whoami.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/config.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/metadata_common.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/saml.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/table.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/types.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/utils.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/validators.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/cli/webserver.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/conftest.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/__init__.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/api/__init__.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/api/distros.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/api/entitlements.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/api/exceptions.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/api/files.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/api/init.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/api/metadata.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/api/metrics.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/api/orgs.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/api/packages.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/api/quota.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/api/rates.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/api/repos.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/api/status.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/api/upstreams.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/api/user.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/api/version.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/api/vulnerabilities.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/cache_utils.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/config.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/credentials/__init__.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/credentials/chain.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/credentials/models.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/credentials/oidc/__init__.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/credentials/oidc/cache.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/credentials/oidc/detectors/__init__.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/credentials/oidc/detectors/azure_devops.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/credentials/oidc/detectors/base.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/credentials/oidc/detectors/bitbucket_pipelines.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/credentials/oidc/detectors/circleci.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/credentials/oidc/detectors/generic.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/credentials/oidc/detectors/github_actions.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/credentials/oidc/detectors/gitlab_ci.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/credentials/oidc/exchange.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/credentials/provider.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/credentials/providers/__init__.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/credentials/providers/cli_flag.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/credentials/providers/credentials_file.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/credentials/providers/env_var.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/credentials/providers/keyring_provider.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/credentials/providers/oidc_provider.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/download.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/mcp/__init__.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/mcp/data.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/mcp/server.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/pagination.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/ratelimits.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/rest.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/utils.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/core/version.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/credential_helpers/__init__.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/credential_helpers/backends.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/credential_helpers/common.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/credential_helpers/custom_domains.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/credential_helpers/default_domains.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/credential_helpers/docker/__init__.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/credential_helpers/docker/installer.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/credential_helpers/docker/runtime.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/credential_helpers/generic.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/credential_helpers/launchers.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/data/config.ini +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/data/credentials.ini +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/templates/__init__.py +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/templates/auth_error.html +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli/templates/auth_success.html +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli.egg-info/dependency_links.txt +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli.egg-info/entry_points.txt +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/cloudsmith_cli.egg-info/top_level.txt +0 -0
- {cloudsmith_cli-1.23.0 → cloudsmith_cli-1.24.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cloudsmith-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.24.0
|
|
4
4
|
Summary: Cloudsmith Command-Line Interface (CLI)
|
|
5
5
|
Author-email: Cloudsmith Ltd <support@cloudsmith.io>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -23,7 +23,7 @@ Classifier: Topic :: Utilities
|
|
|
23
23
|
Requires-Python: >=3.10.0
|
|
24
24
|
Description-Content-Type: text/markdown
|
|
25
25
|
License-File: LICENSE
|
|
26
|
-
Requires-Dist: click!=8.3.0,>=8.
|
|
26
|
+
Requires-Dist: click!=8.3.0,>=8.2
|
|
27
27
|
Requires-Dist: click-configfile>=0.2.3
|
|
28
28
|
Requires-Dist: click-didyoumean>=0.0.3
|
|
29
29
|
Requires-Dist: click-spinner>=0.1.7
|
|
@@ -367,13 +367,21 @@ You can authenticate using your organization's SAML provider, if configured, wit
|
|
|
367
367
|
```
|
|
368
368
|
cloudsmith auth --owner example
|
|
369
369
|
Beginning authentication for the example org ...
|
|
370
|
-
|
|
370
|
+
Your organization's SAML IDP URL is: https://example.com/some-saml-idp
|
|
371
371
|
|
|
372
372
|
Starting webserver to begin authentication ...
|
|
373
373
|
|
|
374
374
|
Authentication complete
|
|
375
375
|
```
|
|
376
376
|
|
|
377
|
+
The CLI opens the IDP URL in your browser automatically. If it can't (for example in Cygwin or a headless shell), it tells you to open the URL above manually and carries on waiting for the callback. Use `--no-browser` to skip the automatic launch entirely:
|
|
378
|
+
|
|
379
|
+
```
|
|
380
|
+
cloudsmith auth --owner example --no-browser
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
*Note:* The authentication callback is served on `127.0.0.1:12400`, so the browser you open the URL in must be running on the same machine as the CLI.
|
|
384
|
+
|
|
377
385
|
#### Getting Your API Key
|
|
378
386
|
|
|
379
387
|
You can retrieve your API key using the `cloudsmith login` command:
|
|
@@ -411,6 +419,12 @@ For example, if you wanted to upload a Debian package, you can do it in one-step
|
|
|
411
419
|
cloudsmith push deb your-org/your-repo/ubuntu/xenial libxml2-2.9.4-2.x86_64.deb
|
|
412
420
|
```
|
|
413
421
|
|
|
422
|
+
To upload a Debian *source* package, give it the `.dsc`. The source and Debian packaging archives listed in the `.dsc` are found and uploaded with it:
|
|
423
|
+
|
|
424
|
+
```
|
|
425
|
+
cloudsmith push deb your-org/your-repo/ubuntu/xenial hello_2.10-3.dsc
|
|
426
|
+
```
|
|
427
|
+
|
|
414
428
|
Want to know how to do it with another packaging format? Easy, just ask for help:
|
|
415
429
|
|
|
416
430
|
```
|
|
@@ -319,13 +319,21 @@ You can authenticate using your organization's SAML provider, if configured, wit
|
|
|
319
319
|
```
|
|
320
320
|
cloudsmith auth --owner example
|
|
321
321
|
Beginning authentication for the example org ...
|
|
322
|
-
|
|
322
|
+
Your organization's SAML IDP URL is: https://example.com/some-saml-idp
|
|
323
323
|
|
|
324
324
|
Starting webserver to begin authentication ...
|
|
325
325
|
|
|
326
326
|
Authentication complete
|
|
327
327
|
```
|
|
328
328
|
|
|
329
|
+
The CLI opens the IDP URL in your browser automatically. If it can't (for example in Cygwin or a headless shell), it tells you to open the URL above manually and carries on waiting for the callback. Use `--no-browser` to skip the automatic launch entirely:
|
|
330
|
+
|
|
331
|
+
```
|
|
332
|
+
cloudsmith auth --owner example --no-browser
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
*Note:* The authentication callback is served on `127.0.0.1:12400`, so the browser you open the URL in must be running on the same machine as the CLI.
|
|
336
|
+
|
|
329
337
|
#### Getting Your API Key
|
|
330
338
|
|
|
331
339
|
You can retrieve your API key using the `cloudsmith login` command:
|
|
@@ -363,6 +371,12 @@ For example, if you wanted to upload a Debian package, you can do it in one-step
|
|
|
363
371
|
cloudsmith push deb your-org/your-repo/ubuntu/xenial libxml2-2.9.4-2.x86_64.deb
|
|
364
372
|
```
|
|
365
373
|
|
|
374
|
+
To upload a Debian *source* package, give it the `.dsc`. The source and Debian packaging archives listed in the `.dsc` are found and uploaded with it:
|
|
375
|
+
|
|
376
|
+
```
|
|
377
|
+
cloudsmith push deb your-org/your-repo/ubuntu/xenial hello_2.10-3.dsc
|
|
378
|
+
```
|
|
379
|
+
|
|
366
380
|
Want to know how to do it with another packaging format? Easy, just ask for help:
|
|
367
381
|
|
|
368
382
|
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Cloudsmith CLI - Main script."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from .cli.commands.main import main
|
|
6
|
+
|
|
7
|
+
if __name__ == "__main__":
|
|
8
|
+
# sys.exit() is required: AliasGroup.main runs click with
|
|
9
|
+
# standalone_mode=False, so click returns the exit code (e.g. from
|
|
10
|
+
# ctx.exit()) instead of raising SystemExit. The console script and the
|
|
11
|
+
# PyInstaller entry point wrap main() in sys.exit() too; a bare main()
|
|
12
|
+
# call would discard the code and always exit 0.
|
|
13
|
+
# Disable false positive for parameters handled by click.
|
|
14
|
+
# pylint: disable=no-value-for-parameter
|
|
15
|
+
sys.exit(main())
|
|
@@ -17,7 +17,7 @@ AUTH_SERVER_PORT = 12400
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def _perform_saml_authentication(
|
|
20
|
-
opts, owner, enable_token_creation=False, use_stderr=False
|
|
20
|
+
opts, owner, enable_token_creation=False, use_stderr=False, no_browser=False
|
|
21
21
|
):
|
|
22
22
|
"""Perform SAML authentication via web browser and local web server."""
|
|
23
23
|
session = create_configured_session(opts)
|
|
@@ -26,11 +26,32 @@ def _perform_saml_authentication(
|
|
|
26
26
|
idp_url = get_idp_url(api_host, owner, session=session)
|
|
27
27
|
|
|
28
28
|
click.echo(
|
|
29
|
-
f"
|
|
29
|
+
f"Your organization's SAML IDP URL is: {click.style(idp_url, bold=True)}",
|
|
30
30
|
err=use_stderr,
|
|
31
31
|
)
|
|
32
32
|
click.echo(err=use_stderr)
|
|
33
|
-
|
|
33
|
+
|
|
34
|
+
if no_browser:
|
|
35
|
+
click.echo(
|
|
36
|
+
"Skipping automatic browser launch (--no-browser). "
|
|
37
|
+
"Please open the URL above manually to continue.",
|
|
38
|
+
err=use_stderr,
|
|
39
|
+
)
|
|
40
|
+
else:
|
|
41
|
+
try:
|
|
42
|
+
browser_opened = webbrowser.open(idp_url)
|
|
43
|
+
except Exception:
|
|
44
|
+
# Browser launch failures vary by platform (webbrowser.Error on
|
|
45
|
+
# Cygwin/headless, anything else elsewhere), so catch broadly and
|
|
46
|
+
# fall back to the manual URL rather than crashing.
|
|
47
|
+
browser_opened = False
|
|
48
|
+
|
|
49
|
+
if not browser_opened:
|
|
50
|
+
click.echo(
|
|
51
|
+
"Couldn't open a browser automatically. "
|
|
52
|
+
"Please open the URL above manually to continue.",
|
|
53
|
+
err=use_stderr,
|
|
54
|
+
)
|
|
34
55
|
|
|
35
56
|
click.echo("Starting webserver to begin authentication ... ", err=use_stderr)
|
|
36
57
|
|
|
@@ -91,12 +112,27 @@ def _perform_saml_authentication(
|
|
|
91
112
|
help="Retrieve API token (auto-creates or auto-rotates, no prompts). "
|
|
92
113
|
"Warning: If token exists, this will rotate it and invalidate the old key.",
|
|
93
114
|
)
|
|
115
|
+
@click.option(
|
|
116
|
+
"--no-browser",
|
|
117
|
+
default=False,
|
|
118
|
+
is_flag=True,
|
|
119
|
+
help="Don't try to open a browser automatically; print the SAML IDP URL to "
|
|
120
|
+
"open manually instead.",
|
|
121
|
+
)
|
|
94
122
|
@decorators.common_cli_config_options
|
|
95
123
|
@decorators.common_cli_output_options
|
|
96
124
|
@decorators.initialise_api
|
|
97
125
|
@click.pass_context
|
|
98
126
|
def authenticate(
|
|
99
|
-
ctx,
|
|
127
|
+
ctx,
|
|
128
|
+
opts,
|
|
129
|
+
owner,
|
|
130
|
+
token,
|
|
131
|
+
force,
|
|
132
|
+
save_config,
|
|
133
|
+
json,
|
|
134
|
+
request_api_key_flag,
|
|
135
|
+
no_browser,
|
|
100
136
|
):
|
|
101
137
|
"""Authenticate to Cloudsmith using the org's SAML setup."""
|
|
102
138
|
# Validate mutual exclusivity
|
|
@@ -150,6 +186,7 @@ def authenticate(
|
|
|
150
186
|
owner,
|
|
151
187
|
enable_token_creation=enable_token_creation,
|
|
152
188
|
use_stderr=use_stderr,
|
|
189
|
+
no_browser=no_browser,
|
|
153
190
|
)
|
|
154
191
|
|
|
155
192
|
if request_api_key_flag:
|
|
@@ -38,6 +38,7 @@ from ...core.api.packages import (
|
|
|
38
38
|
validate_create_package as api_validate_create_package,
|
|
39
39
|
)
|
|
40
40
|
from .. import command, decorators, utils, validators
|
|
41
|
+
from ..dsc_parser import resolve_dsc_files
|
|
41
42
|
from ..exceptions import handle_api_exceptions
|
|
42
43
|
from ..metadata_common import (
|
|
43
44
|
MetadataContentError,
|
|
@@ -75,6 +76,10 @@ METADATA_KWARG_NAMES = (
|
|
|
75
76
|
#: separately from the metadata payload kwargs so it does not leak into the
|
|
76
77
|
#: package-create API call.
|
|
77
78
|
METADATA_FAILURE_MODE_KWARG = "cli_metadata_failure_mode"
|
|
79
|
+
#: Filename suffix of a Debian source control file. A ``deb`` push whose
|
|
80
|
+
#: PACKAGE_FILE carries it is a source-package upload, so its members can be
|
|
81
|
+
#: derived without ``--dsc-file`` (GitHub issue #56).
|
|
82
|
+
DSC_SUFFIX = ".dsc"
|
|
78
83
|
|
|
79
84
|
|
|
80
85
|
def _metadata_failure_is_warn(opts=None):
|
|
@@ -1019,6 +1024,13 @@ def upload_files_and_create_package(
|
|
|
1019
1024
|
return slug_perm, slug
|
|
1020
1025
|
|
|
1021
1026
|
|
|
1027
|
+
def _implied_dsc_file(package_file):
|
|
1028
|
+
"""Return ``package_file`` if it is itself a Debian ``.dsc``, else None."""
|
|
1029
|
+
if isinstance(package_file, str) and package_file.endswith(DSC_SUFFIX):
|
|
1030
|
+
return package_file
|
|
1031
|
+
return None
|
|
1032
|
+
|
|
1033
|
+
|
|
1022
1034
|
def create_push_handlers():
|
|
1023
1035
|
"""Create a handler for upload per package format."""
|
|
1024
1036
|
# pylint: disable=fixme
|
|
@@ -1181,6 +1193,38 @@ def create_push_handlers():
|
|
|
1181
1193
|
parameters = context.get(ctx.info_name)
|
|
1182
1194
|
kwargs["package_type"] = ctx.info_name
|
|
1183
1195
|
|
|
1196
|
+
# deb-only: derive the upstream/native source archive and Debian
|
|
1197
|
+
# packaging archive from a .dsc (see GitHub issue #56). Only the
|
|
1198
|
+
# `deb` subcommand ever registers --dsc-file (below), so this is
|
|
1199
|
+
# a no-op kwargs.pop() for every other format.
|
|
1200
|
+
dsc_file = kwargs.pop("dsc_file", None)
|
|
1201
|
+
if dsc_file is None and not kwargs.get("sources_file"):
|
|
1202
|
+
# Pushing a Debian source package means passing the .dsc as
|
|
1203
|
+
# PACKAGE_FILE, and the API rejects it without a sources
|
|
1204
|
+
# archive, so parse it by default rather than making the user
|
|
1205
|
+
# name the same file twice. An explicit --sources-file means
|
|
1206
|
+
# the caller is driving the members manually; leave them to it.
|
|
1207
|
+
dsc_file = _implied_dsc_file(kwargs.get("package_file"))
|
|
1208
|
+
if dsc_file:
|
|
1209
|
+
resolved_dsc = resolve_dsc_files(dsc_file)
|
|
1210
|
+
if resolved_dsc.ignored_files:
|
|
1211
|
+
click.secho(
|
|
1212
|
+
"Not uploading {files}: the deb package format has no "
|
|
1213
|
+
"field for detached upstream signatures.".format(
|
|
1214
|
+
files=", ".join(resolved_dsc.ignored_files)
|
|
1215
|
+
),
|
|
1216
|
+
fg="yellow",
|
|
1217
|
+
err=utils.should_use_stderr(opts),
|
|
1218
|
+
)
|
|
1219
|
+
# Precedence: explicit --sources-file/--changes-file always
|
|
1220
|
+
# win over values derived from --dsc-file. A user who passes
|
|
1221
|
+
# both wants a manual override, not to have their explicit
|
|
1222
|
+
# flag silently clobbered.
|
|
1223
|
+
if not kwargs.get("sources_file"):
|
|
1224
|
+
kwargs["sources_file"] = resolved_dsc.sources_file
|
|
1225
|
+
if not kwargs.get("changes_file"):
|
|
1226
|
+
kwargs["changes_file"] = resolved_dsc.changes_file
|
|
1227
|
+
|
|
1184
1228
|
owner_repo = kwargs.pop("owner_repo")
|
|
1185
1229
|
if "distribution" in parameters:
|
|
1186
1230
|
kwargs["distribution"] = "/".join(owner_repo[2:])
|
|
@@ -1307,6 +1351,39 @@ def create_push_handlers():
|
|
|
1307
1351
|
)
|
|
1308
1352
|
push_handler = decorator(push_handler)
|
|
1309
1353
|
|
|
1354
|
+
if key == "deb":
|
|
1355
|
+
# deb-only shim (GitHub issue #56): the generic per-format loop
|
|
1356
|
+
# above is driven entirely by the API's package-format model, so
|
|
1357
|
+
# this stays layered on top rather than becoming a new branch in
|
|
1358
|
+
# that generic system.
|
|
1359
|
+
push_handler = click.option(
|
|
1360
|
+
"--dsc-file",
|
|
1361
|
+
"dsc_file",
|
|
1362
|
+
type=ExpandPath(
|
|
1363
|
+
dir_okay=False, exists=True, writable=False, resolve_path=True
|
|
1364
|
+
),
|
|
1365
|
+
default=None,
|
|
1366
|
+
help=(
|
|
1367
|
+
"Path to a Debian .dsc control file, plain or "
|
|
1368
|
+
"OpenPGP-clearsigned. Only needed to point at a .dsc "
|
|
1369
|
+
"other than PACKAGE_FILE: pushing a source package means "
|
|
1370
|
+
"passing the .dsc as PACKAGE_FILE, and that is parsed "
|
|
1371
|
+
"automatically unless --sources-file is given. Its "
|
|
1372
|
+
"'Checksums-Sha256:' (or 'Files:') field supplies "
|
|
1373
|
+
"--sources-file with the upstream/native source archive "
|
|
1374
|
+
"and --changes-file with the Debian packaging archive "
|
|
1375
|
+
"(.debian.tar.* or .diff.gz). Supports the 1.0, 2.0, "
|
|
1376
|
+
"3.0 (native) and 3.0 (quilt) source formats, and the "
|
|
1377
|
+
"referenced files must sit next to the .dsc. An explicit "
|
|
1378
|
+
"--sources-file or --changes-file always takes precedence "
|
|
1379
|
+
"over the derived value for that field. Detached upstream "
|
|
1380
|
+
"signatures (*.asc) are skipped with a warning; a "
|
|
1381
|
+
"multi-component source package (*.orig-*.tar.*) is "
|
|
1382
|
+
"rejected, because the deb package format has no field "
|
|
1383
|
+
"for the extra components."
|
|
1384
|
+
),
|
|
1385
|
+
)(push_handler)
|
|
1386
|
+
|
|
1310
1387
|
handlers[key] = push_handler
|
|
1311
1388
|
|
|
1312
1389
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""CLI - Decorators."""
|
|
2
2
|
|
|
3
3
|
import functools
|
|
4
|
+
import logging
|
|
4
5
|
import os
|
|
5
6
|
|
|
6
7
|
import click
|
|
@@ -28,6 +29,27 @@ def report_retry(seconds, context=None):
|
|
|
28
29
|
)
|
|
29
30
|
|
|
30
31
|
|
|
32
|
+
_DEBUG_LOG_HANDLER_NAME = "cloudsmith-cli-debug"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _configure_debug_logging(enabled):
|
|
36
|
+
"""Install or remove the stderr handler for this package's debug records."""
|
|
37
|
+
package_logger = logging.getLogger("cloudsmith_cli")
|
|
38
|
+
for handler in list(package_logger.handlers):
|
|
39
|
+
if handler.name == _DEBUG_LOG_HANDLER_NAME:
|
|
40
|
+
package_logger.removeHandler(handler)
|
|
41
|
+
if not enabled:
|
|
42
|
+
package_logger.setLevel(logging.NOTSET)
|
|
43
|
+
package_logger.propagate = True
|
|
44
|
+
return
|
|
45
|
+
package_logger.setLevel(logging.DEBUG)
|
|
46
|
+
package_logger.propagate = False
|
|
47
|
+
handler = logging.StreamHandler()
|
|
48
|
+
handler.name = _DEBUG_LOG_HANDLER_NAME
|
|
49
|
+
handler.setFormatter(logging.Formatter("%(name)s: %(message)s"))
|
|
50
|
+
package_logger.addHandler(handler)
|
|
51
|
+
|
|
52
|
+
|
|
31
53
|
def _pop_boolean_flag(kwargs, name, invert=False):
|
|
32
54
|
"""Pop a boolean flag from kwargs, optionally inverting it."""
|
|
33
55
|
value = kwargs.pop(name)
|
|
@@ -158,7 +180,8 @@ def common_cli_output_options(f):
|
|
|
158
180
|
def wrapper(ctx, *args, **kwargs):
|
|
159
181
|
# pylint: disable=missing-docstring
|
|
160
182
|
opts = config.get_or_create_options(ctx)
|
|
161
|
-
opts.debug = kwargs.pop("debug")
|
|
183
|
+
opts.debug = kwargs.pop("debug") or opts.debug
|
|
184
|
+
_configure_debug_logging(opts.debug)
|
|
162
185
|
opts.output = kwargs.pop("output_format")
|
|
163
186
|
opts.verbose = kwargs.pop("verbose")
|
|
164
187
|
kwargs["opts"] = opts
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"""Parse Debian ``.dsc`` control files for ``push deb`` source uploads."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from email.parser import Parser
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
_CLEAR_SIGNED_MESSAGE = "-----BEGIN PGP SIGNED MESSAGE-----"
|
|
10
|
+
_SIGNATURE = "-----BEGIN PGP SIGNATURE-----"
|
|
11
|
+
_DETACHED_SIGNATURE_SUFFIX = ".asc"
|
|
12
|
+
_FILE_LIST_FIELDS = ("Checksums-Sha256", "Files")
|
|
13
|
+
_QUILT_FORMATS = {"2.0", "3.0 (quilt)"}
|
|
14
|
+
_NATIVE_FORMAT = "3.0 (native)"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class ResolvedDscFiles:
|
|
19
|
+
"""Source-package members resolved from a Debian ``.dsc``."""
|
|
20
|
+
|
|
21
|
+
sources_file: str
|
|
22
|
+
changes_file: str | None = None
|
|
23
|
+
#: Members deliberately left out of the upload, for the caller to report.
|
|
24
|
+
ignored_files: tuple[str, ...] = field(default_factory=tuple)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _usage_error(dsc_path, message):
|
|
28
|
+
# Names the file rather than the option: the .dsc is just as often taken
|
|
29
|
+
# from PACKAGE_FILE as from an explicit --dsc-file.
|
|
30
|
+
return click.UsageError(f"Debian source control file {dsc_path!r} {message}")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _unwrap_clearsigned_control(text, dsc_path):
|
|
34
|
+
"""Return deb822 control text from an optional OpenPGP cleartext signature."""
|
|
35
|
+
lines = text.splitlines(keepends=True)
|
|
36
|
+
if not lines or lines[0].rstrip("\r\n") != _CLEAR_SIGNED_MESSAGE:
|
|
37
|
+
return text
|
|
38
|
+
|
|
39
|
+
index = 1
|
|
40
|
+
while index < len(lines) and lines[index].rstrip("\r\n"):
|
|
41
|
+
index += 1
|
|
42
|
+
if index == len(lines):
|
|
43
|
+
raise _usage_error(dsc_path, "has a malformed OpenPGP cleartext signature.")
|
|
44
|
+
|
|
45
|
+
cleartext = []
|
|
46
|
+
for line in lines[index + 1 :]:
|
|
47
|
+
if line.rstrip("\r\n") == _SIGNATURE:
|
|
48
|
+
return "".join(cleartext)
|
|
49
|
+
# RFC 9580 cleartext signatures dash-escape lines beginning with "-".
|
|
50
|
+
cleartext.append(line.removeprefix("- "))
|
|
51
|
+
|
|
52
|
+
raise _usage_error(dsc_path, "has a malformed OpenPGP cleartext signature.")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _read_control_message(dsc_path):
|
|
56
|
+
"""Parse ``dsc_path`` as a plain or OpenPGP-clearsigned deb822 stanza."""
|
|
57
|
+
try:
|
|
58
|
+
with open(dsc_path, encoding="utf-8", errors="replace") as dsc_fh:
|
|
59
|
+
text = dsc_fh.read()
|
|
60
|
+
except OSError as exc:
|
|
61
|
+
raise _usage_error(dsc_path, f"could not be read: {exc}") from exc
|
|
62
|
+
|
|
63
|
+
return Parser().parsestr(_unwrap_clearsigned_control(text, dsc_path))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _parse_file_list(field_name, field_value, dsc_path):
|
|
67
|
+
filenames = []
|
|
68
|
+
for line in field_value.splitlines():
|
|
69
|
+
if not line.strip():
|
|
70
|
+
continue
|
|
71
|
+
parts = line.split()
|
|
72
|
+
if len(parts) != 3:
|
|
73
|
+
raise _usage_error(
|
|
74
|
+
dsc_path,
|
|
75
|
+
f"has a malformed {field_name!r} entry {line.strip()!r}; "
|
|
76
|
+
"expected '<checksum> <size> <filename>'.",
|
|
77
|
+
)
|
|
78
|
+
filenames.append(parts[2])
|
|
79
|
+
return filenames
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _extract_filenames(message, dsc_path):
|
|
83
|
+
"""Return an agreed filename list, preferring the strong SHA-256 field."""
|
|
84
|
+
file_lists = {}
|
|
85
|
+
for field_name in _FILE_LIST_FIELDS:
|
|
86
|
+
field_value = message.get(field_name)
|
|
87
|
+
if field_value:
|
|
88
|
+
filenames = _parse_file_list(field_name, field_value, dsc_path)
|
|
89
|
+
if filenames:
|
|
90
|
+
file_lists[field_name] = filenames
|
|
91
|
+
|
|
92
|
+
if not file_lists:
|
|
93
|
+
raise _usage_error(
|
|
94
|
+
dsc_path,
|
|
95
|
+
"has no non-empty 'Checksums-Sha256:' or 'Files:' field to parse.",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
for filenames in file_lists.values():
|
|
99
|
+
if len(filenames) != len(set(filenames)):
|
|
100
|
+
raise _usage_error(dsc_path, "lists the same source-package member twice.")
|
|
101
|
+
|
|
102
|
+
selected_name = next(iter(file_lists))
|
|
103
|
+
selected = file_lists[selected_name]
|
|
104
|
+
for field_name, filenames in file_lists.items():
|
|
105
|
+
if set(filenames) != set(selected):
|
|
106
|
+
raise _usage_error(
|
|
107
|
+
dsc_path,
|
|
108
|
+
f"has conflicting filenames in {selected_name!r} and {field_name!r}.",
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
return selected
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _required_field(message, field_name, dsc_path):
|
|
115
|
+
value = message.get(field_name)
|
|
116
|
+
if not value or not value.strip():
|
|
117
|
+
raise _usage_error(dsc_path, f"has no {field_name!r} field.")
|
|
118
|
+
return value.strip()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _validate_member_names(filenames, dsc_path):
|
|
122
|
+
for filename in filenames:
|
|
123
|
+
if os.path.isabs(filename) or os.path.basename(filename) != filename:
|
|
124
|
+
raise _usage_error(
|
|
125
|
+
dsc_path,
|
|
126
|
+
f"references invalid member filename {filename!r}; source-package "
|
|
127
|
+
"members must be filenames next to the .dsc, not paths.",
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _is_tar_archive(filename, stem):
|
|
132
|
+
prefix = f"{stem}.tar."
|
|
133
|
+
return filename.startswith(prefix) and len(filename) > len(prefix)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _classify_members(message, filenames, dsc_path):
|
|
137
|
+
"""Map source-package members to the two fields accepted by the SDK."""
|
|
138
|
+
source = _required_field(message, "Source", dsc_path)
|
|
139
|
+
version = _required_field(message, "Version", dsc_path).split(":", 1)[-1]
|
|
140
|
+
source_format = _required_field(message, "Format", dsc_path)
|
|
141
|
+
upstream_version = version.rsplit("-", 1)[0]
|
|
142
|
+
|
|
143
|
+
# Detached upstream signatures (e.g. hello_2.10.orig.tar.gz.asc) are
|
|
144
|
+
# common and have no field in the deb upload model. Nothing in the
|
|
145
|
+
# uploaded source is lost by leaving them out, so they are skipped with a
|
|
146
|
+
# warning rather than failing the push.
|
|
147
|
+
signature_files = [
|
|
148
|
+
filename
|
|
149
|
+
for filename in filenames
|
|
150
|
+
if filename.endswith(_DETACHED_SIGNATURE_SUFFIX)
|
|
151
|
+
]
|
|
152
|
+
# Classify the uploadable members only. A signature shares its tarball's
|
|
153
|
+
# stem (`*.orig.tar.gz.asc`), so leaving it in would match as a second
|
|
154
|
+
# source archive.
|
|
155
|
+
members = [f for f in filenames if f not in set(signature_files)]
|
|
156
|
+
|
|
157
|
+
sources = []
|
|
158
|
+
changes = []
|
|
159
|
+
component_files = []
|
|
160
|
+
legacy_non_native = False
|
|
161
|
+
|
|
162
|
+
if source_format in _QUILT_FORMATS:
|
|
163
|
+
orig_stem = f"{source}_{upstream_version}.orig"
|
|
164
|
+
component_prefix = f"{orig_stem}-"
|
|
165
|
+
debian_stem = f"{source}_{version}.debian"
|
|
166
|
+
sources = [f for f in members if _is_tar_archive(f, orig_stem)]
|
|
167
|
+
changes = [f for f in members if _is_tar_archive(f, debian_stem)]
|
|
168
|
+
component_files = [
|
|
169
|
+
f
|
|
170
|
+
for f in members
|
|
171
|
+
if f.startswith(component_prefix) and ".tar." in f[len(component_prefix) :]
|
|
172
|
+
]
|
|
173
|
+
elif source_format == "1.0":
|
|
174
|
+
orig_stem = f"{source}_{upstream_version}.orig"
|
|
175
|
+
diff_name = f"{source}_{version}.diff.gz"
|
|
176
|
+
native_stem = f"{source}_{version}"
|
|
177
|
+
orig_files = [f for f in members if _is_tar_archive(f, orig_stem)]
|
|
178
|
+
diff_files = [f for f in members if f == diff_name]
|
|
179
|
+
native_files = [f for f in members if _is_tar_archive(f, native_stem)]
|
|
180
|
+
if orig_files or diff_files:
|
|
181
|
+
legacy_non_native = True
|
|
182
|
+
sources = orig_files
|
|
183
|
+
changes = diff_files
|
|
184
|
+
else:
|
|
185
|
+
sources = native_files
|
|
186
|
+
elif source_format == _NATIVE_FORMAT:
|
|
187
|
+
sources = [f for f in members if _is_tar_archive(f, f"{source}_{version}")]
|
|
188
|
+
else:
|
|
189
|
+
# '3.0 (git)' ships a git bundle and '3.0 (bzr)' a VCS tarball, neither
|
|
190
|
+
# of which is a source archive the deb upload model can index, so both
|
|
191
|
+
# fall through to the unsupported-format error alongside
|
|
192
|
+
# '3.0 (custom)'.
|
|
193
|
+
raise _usage_error(
|
|
194
|
+
dsc_path, f"uses unsupported Debian source format {source_format!r}."
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
if component_files:
|
|
198
|
+
raise _usage_error(
|
|
199
|
+
dsc_path,
|
|
200
|
+
"references a multi-component source package ({files}). Cloudsmith "
|
|
201
|
+
"does not support multi-component Debian source packages for deb "
|
|
202
|
+
"uploads.".format(files=", ".join(component_files)),
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
classified = set(sources + changes + component_files)
|
|
206
|
+
unexpected = [f for f in members if f not in classified]
|
|
207
|
+
if unexpected:
|
|
208
|
+
raise _usage_error(
|
|
209
|
+
dsc_path,
|
|
210
|
+
"contains unsupported or incorrectly named source-package member(s) "
|
|
211
|
+
f"({', '.join(unexpected)}) for format {source_format!r}.",
|
|
212
|
+
)
|
|
213
|
+
if len(sources) != 1:
|
|
214
|
+
raise _usage_error(
|
|
215
|
+
dsc_path,
|
|
216
|
+
f"must reference exactly one main source archive for format "
|
|
217
|
+
f"{source_format!r}; found {len(sources)}.",
|
|
218
|
+
)
|
|
219
|
+
if source_format in _QUILT_FORMATS and len(changes) != 1:
|
|
220
|
+
raise _usage_error(
|
|
221
|
+
dsc_path,
|
|
222
|
+
f"must reference exactly one Debian packaging archive for format "
|
|
223
|
+
f"{source_format!r}; found {len(changes)}.",
|
|
224
|
+
)
|
|
225
|
+
if legacy_non_native and len(changes) != 1:
|
|
226
|
+
raise _usage_error(
|
|
227
|
+
dsc_path,
|
|
228
|
+
"must reference exactly one Debian packaging diff for non-native "
|
|
229
|
+
f"format '1.0'; found {len(changes)}.",
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
return sources[0], changes[0] if changes else None, tuple(signature_files)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _resolve_member(base_dir, filename, dsc_path):
|
|
236
|
+
"""Canonicalise a member that ``_validate_member_names`` has vetted."""
|
|
237
|
+
candidate = os.path.join(base_dir, filename)
|
|
238
|
+
if not os.path.isfile(candidate):
|
|
239
|
+
raise _usage_error(
|
|
240
|
+
dsc_path,
|
|
241
|
+
f"references {filename!r}, but it is not a regular file next to the .dsc.",
|
|
242
|
+
)
|
|
243
|
+
# Uploading the canonical path means a symlink swapped between this check
|
|
244
|
+
# and the upload cannot redirect the read. The link itself may point
|
|
245
|
+
# outside the directory: `mk-origtargz --symlink` (the uscan default)
|
|
246
|
+
# routinely symlinks the .orig tarball in from a download cache.
|
|
247
|
+
return os.path.realpath(candidate)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def resolve_dsc_files(dsc_path):
|
|
251
|
+
"""Return the :class:`ResolvedDscFiles` derived from a Debian ``.dsc``."""
|
|
252
|
+
message = _read_control_message(dsc_path)
|
|
253
|
+
filenames = _extract_filenames(message, dsc_path)
|
|
254
|
+
_validate_member_names(filenames, dsc_path)
|
|
255
|
+
source_filename, changes_filename, ignored_files = _classify_members(
|
|
256
|
+
message, filenames, dsc_path
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
base_dir = os.path.realpath(os.path.dirname(os.path.abspath(dsc_path)))
|
|
260
|
+
return ResolvedDscFiles(
|
|
261
|
+
sources_file=_resolve_member(base_dir, source_filename, dsc_path),
|
|
262
|
+
changes_file=(
|
|
263
|
+
_resolve_member(base_dir, changes_filename, dsc_path)
|
|
264
|
+
if changes_filename
|
|
265
|
+
else None
|
|
266
|
+
),
|
|
267
|
+
ignored_files=ignored_files,
|
|
268
|
+
)
|
|
@@ -171,8 +171,9 @@ def get_401_error_hint(ctx, opts, exc):
|
|
|
171
171
|
|
|
172
172
|
if credential:
|
|
173
173
|
return (
|
|
174
|
-
"
|
|
175
|
-
"
|
|
174
|
+
"This usually means your API key is invalid, expired, or "
|
|
175
|
+
"lacks access to this resource - check your credentials and "
|
|
176
|
+
"try again."
|
|
176
177
|
)
|
|
177
178
|
|
|
178
179
|
if ctx.info_name == "token":
|
|
@@ -20,6 +20,38 @@ logger = logging.getLogger(__name__)
|
|
|
20
20
|
DEFAULT_AUDIENCE = "cloudsmith"
|
|
21
21
|
|
|
22
22
|
|
|
23
|
+
def _resolve_region(session) -> str | None:
|
|
24
|
+
"""Resolve the AWS region with the AWS CLI precedence.
|
|
25
|
+
|
|
26
|
+
The order is: explicit session config, AWS_REGION, AWS_DEFAULT_REGION,
|
|
27
|
+
the config file region, then EC2 instance metadata.
|
|
28
|
+
"""
|
|
29
|
+
try:
|
|
30
|
+
# Lazy imports: botocore is an optional dependency (the aws extra).
|
|
31
|
+
from botocore.configprovider import ConfigChainFactory
|
|
32
|
+
from botocore.exceptions import BotoCoreError
|
|
33
|
+
from botocore.utils import BadIMDSRequestError, IMDSRegionProvider
|
|
34
|
+
except ImportError:
|
|
35
|
+
logger.debug("AWSDetector: region providers unavailable", exc_info=True)
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
# No public accessor exists for the underlying botocore session.
|
|
39
|
+
botocore_session = session._session # pylint: disable=protected-access
|
|
40
|
+
chain = ConfigChainFactory(session=botocore_session).create_config_chain(
|
|
41
|
+
instance_name="region",
|
|
42
|
+
env_var_names=["AWS_REGION", "AWS_DEFAULT_REGION"],
|
|
43
|
+
config_property_names="region",
|
|
44
|
+
)
|
|
45
|
+
region = chain.provide()
|
|
46
|
+
if region:
|
|
47
|
+
return region
|
|
48
|
+
try:
|
|
49
|
+
return IMDSRegionProvider(session=botocore_session).provide()
|
|
50
|
+
except (BotoCoreError, BadIMDSRequestError):
|
|
51
|
+
logger.debug("AWSDetector: IMDS region lookup failed", exc_info=True)
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
23
55
|
class AWSDetector(EnvironmentDetector):
|
|
24
56
|
"""Detects AWS environments and obtains a JWT via STS GetWebIdentityToken."""
|
|
25
57
|
|
|
@@ -71,7 +103,7 @@ class AWSDetector(EnvironmentDetector):
|
|
|
71
103
|
|
|
72
104
|
audience = self.context.oidc_audience or DEFAULT_AUDIENCE
|
|
73
105
|
session = self._session or boto3.Session()
|
|
74
|
-
sts = session.client("sts")
|
|
106
|
+
sts = session.client("sts", region_name=_resolve_region(session))
|
|
75
107
|
response = sts.get_web_identity_token(
|
|
76
108
|
Audience=[audience],
|
|
77
109
|
SigningAlgorithm="RS256",
|