Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 1.6.2
current_version = 1.7.0
commit = True
tag = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<revision>\d+)
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

## [1.7.0] - 2025-03-31

### Added

- Added `--extra-files` parameter for Maven upload command ([#190](https://github.com/cloudsmith-io/cloudsmith-cli/pull/190))

## [1.6.2] - 2025-03-27

- Added html templates for saml response endpoints
Expand Down
79 changes: 60 additions & 19 deletions cloudsmith_cli/cli/commands/push.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,17 +352,33 @@ def upload_files_and_create_package(
# 2. Validate file upload parameters
md5_checksums = {}
for k, v in kwargs.items():
if not v or not k.endswith("_file"):
if not v:
continue

md5_checksums[k] = validate_upload_file(
ctx=ctx,
opts=opts,
owner=owner,
repo=repo,
filepath=v,
skip_errors=skip_errors,
)
# Handle a single file
if k.endswith("_file"):
md5_checksums[k] = validate_upload_file(
ctx=ctx,
opts=opts,
owner=owner,
repo=repo,
filepath=v,
skip_errors=skip_errors,
)

# Check if the key is "extra_files" (to handle multiple files)
if k == "extra_files" and isinstance(v, list):
md5_checksums[k] = [
validate_upload_file(
ctx=ctx,
opts=opts,
owner=owner,
repo=repo,
filepath=file,
skip_errors=skip_errors,
)
for file in v
]

if dry_run:
click.echo()
Expand All @@ -371,18 +387,35 @@ def upload_files_and_create_package(

# 3. Upload any arguments that look like files
for k, v in kwargs.items():
if not v or not k.endswith("_file"):
if not v:
continue

kwargs[k] = upload_file(
ctx=ctx,
opts=opts,
owner=owner,
repo=repo,
filepath=v,
skip_errors=skip_errors,
md5_checksum=md5_checksums[k],
)
# Handle a single file
if k.endswith("_file"):
kwargs[k] = upload_file(
ctx=ctx,
opts=opts,
owner=owner,
repo=repo,
filepath=v,
skip_errors=skip_errors,
md5_checksum=md5_checksums[k],
)

# Check if the key is "extra_files" (to handle multiple files)
if k == "extra_files" and isinstance(v, list):
kwargs[k] = [
upload_file(
ctx=ctx,
opts=opts,
owner=owner,
repo=repo,
filepath=file,
skip_errors=skip_errors,
md5_checksum=md5_checksums[k][idx],
)
for idx, file in enumerate(v)
]

# 4. Create the package with package files and additional arguments
_, slug = create_package(
Expand Down Expand Up @@ -531,6 +564,14 @@ def push_handler(ctx, *args, **kwargs):
option_kwargs["type"] = ExpandPath(
dir_okay=False, exists=True, writable=False, resolve_path=True
)
elif k == "extra_files":
# Handle multiple files for extra_files parameter.
option_kwargs["type"] = str
option_kwargs["multiple"] = True
option_kwargs["callback"] = validators.validate_extra_files_parameter
info["help"] = (
info["help"] + " Accepts a comma-separated list of values."
)
Comment on lines +572 to +574
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API serializer text isn’t specific to the CLI, so I’ve added this to clarify how the input is expected to be formatted.

...
  --artifact-id TEXT              The ID of the artifact.
  --extra-files TEXT              Extra files to include in the package. This
                                  can be a single file or multiple files.
                                  Accepts a comma-separated list of values.
  --group-id TEXT                 Artifact's group ID.
...

elif info["type"] == "bool":
option_name_fmt = "--%(key)s/--no-%(key)s"
option_kwargs["is_flag"] = True
Expand Down
245 changes: 245 additions & 0 deletions cloudsmith_cli/cli/tests/test_push.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
import unittest
from unittest.mock import MagicMock, patch

from ..commands.push import upload_files_and_create_package


# pylint: disable=too-many-instance-attributes
class TestPush(unittest.TestCase):
def setUp(self):
self.mock_ctx = MagicMock()
self.mock_opts = MagicMock()
self.package_type = "test_format"
self.owner = "test_owner"
self.repo = "test_repo"
self.name = "test_package"
self.version = "1.0.0"
self.dry_run = False
self.no_wait_for_sync = False
self.wait_interval = 5.0
self.skip_errors = False
self.sync_attempts = 3

def test_upload_files_and_create_package(self):
# Values passed in from the command line
input_kwargs = {
"package_file": "package/file/path",
"name": "test_package",
"version": "1.0.0",
}

# Predefine file attributes in for testing
files = {
"package_file": {
"path": "package/file/path",
"checksum": "package_file_checksum",
"id": "package_file_identifier",
},
}

# Kwargs for package creation in final step, contain ids returned from the AWS S3 upload
create_package_kwargs = {
"package_file": files["package_file"]["id"],
"name": self.name,
"version": self.version,
}

with (
patch(
"cloudsmith_cli.cli.commands.push.validate_create_package"
) as mock_validate_create_package,
patch(
"cloudsmith_cli.cli.commands.push.validate_upload_file"
) as mock_validate_upload_file,
patch("cloudsmith_cli.cli.commands.push.upload_file") as mock_upload_file,
patch(
"cloudsmith_cli.cli.commands.push.create_package"
) as mock_create_package,
patch("cloudsmith_cli.cli.commands.push.wait_for_package_sync"),
):
# Validate upload returns checksums which we use to upload the files
mock_validate_upload_file.side_effect = [
file["checksum"] for file in files.values()
]
# Upload files returns files ids which we use to create the package
mock_upload_file.side_effect = [file["id"] for file in files.values()]
mock_create_package.return_value = ("", "test_package_slug")

# 1. Call upload_files_and_create_package function
upload_files_and_create_package(
self.mock_ctx,
self.mock_opts,
self.package_type,
[self.owner, self.repo],
self.dry_run,
self.no_wait_for_sync,
self.wait_interval,
self.skip_errors,
self.sync_attempts,
**input_kwargs,
)

# 2. Confirm that validate_create_package was called with the correct arguments
mock_validate_create_package.assert_called_once_with(
ctx=self.mock_ctx,
opts=self.mock_opts,
owner=self.owner,
repo=self.repo,
package_type=self.package_type,
skip_errors=self.skip_errors,
**input_kwargs,
)

# 3. For each file, confirm that validate_upload_file and upload_file were called with the correct arguments
for file_data in files.values():
mock_validate_upload_file.assert_any_call(
ctx=self.mock_ctx,
opts=self.mock_opts,
owner=self.owner,
repo=self.repo,
filepath=file_data["path"],
skip_errors=self.skip_errors,
)
mock_upload_file.assert_any_call(
ctx=self.mock_ctx,
opts=self.mock_opts,
owner=self.owner,
repo=self.repo,
filepath=file_data["path"],
skip_errors=self.skip_errors,
md5_checksum=file_data["checksum"],
)

# 4. Validate that create_package was called once with the correct arguments
mock_create_package.assert_called_once_with(
ctx=self.mock_ctx,
opts=self.mock_opts,
owner=self.owner,
repo=self.repo,
package_type=self.package_type,
skip_errors=self.skip_errors,
**create_package_kwargs,
)

def test_upload_files_and_create_package_extra_files(self):
# Values passed in from the command line
input_kwargs = {
"package_file": "package/file/path",
"test_file": "test/file/path",
"extra_files": ["test/extra/file/path1", "test/extra/file/path2"],
"name": "test_package",
"version": "1.0.0",
}

# Predefine file attributes in for testing
files = {
"package_file": {
"path": "package/file/path",
"checksum": "package_file_checksum",
"id": "package_file_identifier",
},
"test_file": {
"path": "test/file/path",
"checksum": "test_file_checksum",
"id": "test_file_identifier",
},
"extra_file1": {
"path": "test/extra/file/path1",
"checksum": "extra_file_checksum1",
"id": "extra_file_identifier1",
},
"extra_file2": {
"path": "test/extra/file/path2",
"checksum": "extra_file_checksum2",
"id": "extra_file_identifier2",
},
}

# Kwargs for package creation in final step, contain ids returned from the AWS S3 upload
create_package_kwargs = {
"package_file": files["package_file"]["id"],
"test_file": files["test_file"]["id"],
"extra_files": [
files["extra_file1"]["id"],
files["extra_file2"]["id"],
],
"name": self.name,
"version": self.version,
}

with (
patch(
"cloudsmith_cli.cli.commands.push.validate_create_package"
) as mock_validate_create_package,
patch(
"cloudsmith_cli.cli.commands.push.validate_upload_file"
) as mock_validate_upload_file,
patch("cloudsmith_cli.cli.commands.push.upload_file") as mock_upload_file,
patch(
"cloudsmith_cli.cli.commands.push.create_package"
) as mock_create_package,
patch("cloudsmith_cli.cli.commands.push.wait_for_package_sync"),
):
# Validate upload returns checksums which we use to upload the files
mock_validate_upload_file.side_effect = [
file["checksum"] for file in files.values()
]
# Upload files returns files ids which we use to create the package
mock_upload_file.side_effect = [file["id"] for file in files.values()]
mock_create_package.return_value = ("", "test_package_slug")

# 1. Call upload_files_and_create_package function
upload_files_and_create_package(
self.mock_ctx,
self.mock_opts,
self.package_type,
[self.owner, self.repo],
self.dry_run,
self.no_wait_for_sync,
self.wait_interval,
self.skip_errors,
self.sync_attempts,
**input_kwargs,
)

# 2. Confirm that validate_create_package was called with the correct arguments
mock_validate_create_package.assert_called_once_with(
ctx=self.mock_ctx,
opts=self.mock_opts,
owner=self.owner,
repo=self.repo,
package_type=self.package_type,
skip_errors=self.skip_errors,
**input_kwargs,
)

# 3. For each file, confirm that validate_upload_file and upload_file were called with the correct arguments
for file_data in files.values():
mock_validate_upload_file.assert_any_call(
ctx=self.mock_ctx,
opts=self.mock_opts,
owner=self.owner,
repo=self.repo,
filepath=file_data["path"],
skip_errors=self.skip_errors,
)
mock_upload_file.assert_any_call(
ctx=self.mock_ctx,
opts=self.mock_opts,
owner=self.owner,
repo=self.repo,
filepath=file_data["path"],
skip_errors=self.skip_errors,
md5_checksum=file_data["checksum"],
)

# 4. Validate that create_package was called once with the correct arguments
mock_create_package.assert_called_once_with(
ctx=self.mock_ctx,
opts=self.mock_opts,
owner=self.owner,
repo=self.repo,
package_type=self.package_type,
skip_errors=self.skip_errors,
**create_package_kwargs,
)
Loading