Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ htmlcov/
.coverage
.DS_Store
.vscode
api_schema
55 changes: 55 additions & 0 deletions src/jamf_pro_sdk/clients/jcds2.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import logging
import math
import warnings
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import TYPE_CHECKING, Callable, Iterator, Union
Expand Down Expand Up @@ -134,9 +135,57 @@ def _upload_part(s3_client, multipart_upload: dict, part_number: int, file_uploa
logger.debug(part_resp)
return {"PartNumber": part_number, "ETag": part_resp["ETag"]}

def upload_package(self, file_path: Union[str, Path]) -> None:
"""Upload a file and create the package object using the new package upload API.

This method replaces :meth:`upload_file` and does not require the ``aws`` extra dependency.
It uses the ``POST /v1/packages/{id}/upload`` endpoint instead of the deprecated JCDS v1
S3-based workflow.

A ``JCDS2FileExistsError`` is raised if any file of the same name exists and is associated
to a package.

:param file_path: The path to the file to upload. Will raise ``FileNotFoundError`` if the
path to the file's location does not exist.
:type file_path: Union[str, Path]
"""
if not isinstance(file_path, Path):
file_path = Path(file_path)

if not file_path.exists():
raise FileNotFoundError(f"File not found: {file_path}")

packages = [
self.classic_api_client.get_package_by_id(p)
for p in self.classic_api_client.list_all_packages()
]

for p in packages:
if file_path.name == p.filename:
raise JCDS2FileExistsError(
f"The file '{file_path.name}' exists and is associated to package "
f"({p.id}) '{p.name}'"
)

new_package = ClassicPackage(name=file_path.name, filename=file_path.name)
new_pkg_id = self.classic_api_client.create_package(data=new_package)
logger.debug("Created package %s", new_pkg_id)

try:
self.pro_api_client.upload_package_v1(
package_id=new_pkg_id, file_path=file_path
)
except Exception as err:
logger.exception(err)
raise

def upload_file(self, file_path: Union[str, Path]) -> None:
"""Upload a file to the JCDS and create the package object.

.. deprecated::
The JCDS v1 API is deprecated by Jamf (2025-08-28). Use :meth:`upload_package` instead,
which does not require the ``aws`` extra dependency.

If the file is less than 1 GiB in size the upload will be performed in a single request. If
the file is greater than 1 GiB in size a multipart upload operation will be performed.

Expand All @@ -151,6 +200,12 @@ def upload_file(self, file_path: Union[str, Path]) -> None:
to the file's location does not exist.
:type file_path: Union[str, Path]
"""
warnings.warn(
"upload_file() is deprecated. The JCDS v1 API was deprecated by Jamf on 2025-08-28. "
"Use upload_package() instead, which does not require the 'aws' extra dependency.",
DeprecationWarning,
stacklevel=2,
)
if not BOTO3_IS_INSTALLED:
raise ImportError("The 'aws' extra dependency is required.")

Expand Down
Loading