Skip to content
Closed
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
279503e
pkg-sign commands
danimtb Jul 18, 2025
13dd0f7
add sign_tools
danimtb Aug 13, 2025
888fcf5
Merge branch 'develop2' into feature/improve_pkg-sign
danimtb Aug 13, 2025
d8b0454
add output formatters
danimtb Aug 14, 2025
a1d5368
add todo
danimtb Aug 14, 2025
7619da9
add cannonical test
danimtb Aug 14, 2025
61f3eac
Merge branch 'develop2' of github.com:conan-io/conan into feature/imp…
danimtb Nov 11, 2025
3c6019e
improve output
danimtb Nov 12, 2025
39b7c8c
remove deprecated use of methods in pkglist
danimtb Nov 12, 2025
1f861b7
fix help msgs
danimtb Nov 12, 2025
2bde5a3
fix assert
danimtb Nov 12, 2025
4206bf4
raise if functions not found
danimtb Nov 13, 2025
ed780df
pattern same as upload
danimtb Nov 13, 2025
f8bc795
Update conan/cli/commands/cache.py
danimtb Nov 13, 2025
a887c3b
raise error in json output
danimtb Nov 13, 2025
cfca8d0
update output and error management
danimtb Nov 18, 2025
5f7ef03
Merge branch 'feature/improve_pkg-sign' of github.com:danimtb/conan i…
danimtb Nov 18, 2025
6d54cb2
fix unit tests
danimtb Nov 18, 2025
94c5634
fix and rename
danimtb Nov 18, 2025
4c74ec7
clean
danimtb Nov 18, 2025
d23091e
add fixme
danimtb Nov 19, 2025
bc159a4
Merge branch 'develop2' of github.com:conan-io/conan into feature/imp…
danimtb Nov 19, 2025
4c584b7
manage error output
danimtb Nov 19, 2025
e0aacce
save pkg sign to deps graph node
danimtb Nov 20, 2025
7ae2c58
cache sign preparator and install output
danimtb Nov 20, 2025
d7478ea
install output fix
danimtb Nov 20, 2025
5da4d71
fix output
danimtb Nov 20, 2025
207bdc9
fix
danimtb Nov 20, 2025
aa0a89e
fixes
danimtb Nov 20, 2025
4c9b4b4
fix
danimtb Nov 20, 2025
bd4954a
more
danimtb Nov 20, 2025
cc20934
Update conan/tools/pkg_signing/plugin.py
danimtb Nov 21, 2025
c78b2e4
Update conan/tools/pkg_signing/plugin.py
danimtb Nov 21, 2025
2748356
Update conan/cli/commands/cache.py
danimtb Nov 21, 2025
8594b93
Update conan/tools/pkg_signing/plugin.py
danimtb Nov 21, 2025
0289f5e
remove tool
danimtb Nov 21, 2025
5b64682
remove from tests
danimtb Nov 21, 2025
fe427b6
Merge branch 'feature/improve_pkg-sign' of github.com:danimtb/conan i…
danimtb Nov 21, 2025
b09b70e
small fixes
danimtb Nov 21, 2025
649bded
first simplify
danimtb Nov 21, 2025
ef4344b
simplify more
danimtb Nov 21, 2025
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
15 changes: 15 additions & 0 deletions conan/api/subapi/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from conan.errors import ConanException
from conan.api.model import PkgReference
from conan.api.model import RecipeReference
from conan.internal.rest.pkg_sign import PkgSignaturesPlugin
from conan.internal.util.dates import revision_timestamp_now
from conan.internal.util.files import rmdir, mkdir, remove, save

Expand Down Expand Up @@ -76,6 +77,20 @@ def check_integrity(self, package_list):
checker = IntegrityChecker(cache)
checker.check(package_list)

def sign(self, package_list):
"""Sign packages with the signing plugin"""
cache = PkgCache(self._conan_api.cache_folder, self._api_helpers.global_conf)
pkg_signer = PkgSignaturesPlugin(cache, self._conan_api.home_folder)
pkg_signer.sign(package_list, context="cache")
return {"results": package_list.serialize(), "context": "cache", "action": "sign"}

def verify(self, package_list):
"""Verify packages with the signing plugin"""
cache = PkgCache(self._conan_api.cache_folder, self._api_helpers.global_conf)
pkg_signer = PkgSignaturesPlugin(cache, self._conan_api.home_folder)
pkg_signer.verify(package_list, context="cache")
return {"results": package_list.serialize(), "context": "cache", "action": "verify"}

def clean(self, package_list, source=True, build=True, download=True, temp=True,
backup_sources=False):
"""
Expand Down
85 changes: 84 additions & 1 deletion conan/cli/commands/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,39 @@
from conan.api.output import cli_out_write, ConanOutput
from conan.cli import make_abs_path
from conan.cli.command import conan_command, conan_subcommand, OnceArgument
from conan.cli.commands.list import print_list_text, print_list_json
from conan.cli.commands.list import print_list_text, print_list_json, print_serial
from conan.errors import ConanException
from conan.api.model import PkgReference
from conan.api.model import RecipeReference
from conan.internal.rest.pkg_sign import print_cache_sign_verify_text


def json_export(data):
cli_out_write(json.dumps({"cache_path": data}))


def print_cache_sign_verify_json(data):
errors_in_package_sign = False
for ref, revisions in data.get("results").items():
for revision, revision_data in revisions.get("revisions").items():
pkgsign_result = revision_data["package sign"].lower()
if "error" in pkgsign_result or "fail" in pkgsign_result:
errors_in_package_sign = True
if "packages" in revision_data:
for package_id, package_data in revision_data["packages"].items():
for prev, prev_data in package_data["revisions"].items():
pkgsign_result = prev_data["package sign"].lower()
if "error" in pkgsign_result or "fail" in pkgsign_result:
errors_in_package_sign = True

if errors_in_package_sign:
action_msg = "signature verification" if data.get("action") == "verify" else "signing"
data["conan_error"] = f"There were some errors in the {action_msg} process. " \
"Please check the output."
json_data = json.dumps(data, indent=4)
cli_out_write(json_data)


@conan_command(group="Consumer")
def cache(conan_api: ConanAPI, parser, *args):
"""
Expand Down Expand Up @@ -150,6 +173,66 @@ def cache_check_integrity(conan_api: ConanAPI, parser, subparser, *args):
ConanOutput().success("Integrity check: ok")


@conan_subcommand(formatters={"text": print_cache_sign_verify_text,
"json": print_cache_sign_verify_json})
def cache_sign(conan_api: ConanAPI, parser, subparser, *args):
"""
Sign packages with the Package Signing Plugin
"""
subparser.add_argument("pattern", nargs="?",
help="Selection pattern for references to be signed")
subparser.add_argument("-l", "--list", action=OnceArgument,
help="Package list of packages to be signed")
subparser.add_argument('-p', '--package-query', action=OnceArgument,
help="Only the packages matching a specific query, e.g., "
"os=Windows AND (arch=x86 OR compiler=gcc)")
args = parser.parse_args(*args)

if args.pattern is None and args.list is None:
raise ConanException("Missing pattern or package list file")
if args.pattern and args.list:
raise ConanException("Cannot specify both pattern and list")

if args.list:
listfile = make_abs_path(args.list)
multi_package_list = MultiPackagesList.load(listfile)
package_list = multi_package_list["Local Cache"]
else:
ref_pattern = ListPattern(args.pattern, package_id="*")
package_list = conan_api.list.select(ref_pattern, package_query=args.package_query)
return conan_api.cache.sign(package_list)


@conan_subcommand(formatters={"text": print_cache_sign_verify_text,
"json": print_cache_sign_verify_json})
def cache_verify(conan_api: ConanAPI, parser, subparser, *args):
"""
Check the signature of packages with the Package Singing Plugin
"""
subparser.add_argument("pattern", nargs="?",
help="Selection pattern for references to verify their signature")
subparser.add_argument("-l", "--list", action=OnceArgument,
help="Package list of packages to verify their signature")
subparser.add_argument('-p', '--package-query', action=OnceArgument,
help="Only the packages matching a specific query, e.g., "
"os=Windows AND (arch=x86 OR compiler=gcc)")
args = parser.parse_args(*args)

if args.pattern is None and args.list is None:
raise ConanException("Missing pattern or package list file")
if args.pattern and args.list:
raise ConanException("Cannot specify both pattern and list")

if args.list:
listfile = make_abs_path(args.list)
multi_package_list = MultiPackagesList.load(listfile)
package_list = multi_package_list["Local Cache"]
else:
ref_pattern = ListPattern(args.pattern, package_id="*")
package_list = conan_api.list.select(ref_pattern, package_query=args.package_query)
return conan_api.cache.verify(package_list)


@conan_subcommand(formatters={"text": print_list_text,
"json": print_list_json})
def cache_save(conan_api: ConanAPI, parser, subparser, *args):
Expand Down
42 changes: 32 additions & 10 deletions conan/cli/commands/upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ def summary_upload_list(results):
"""
ConanOutput().subtitle("Upload summary")
info = results["results"]
package_sign_error = False

def format_upload(item):
nonlocal package_sign_error
if isinstance(item, dict):
result = {}
for k, v in item.items():
Expand All @@ -24,17 +26,31 @@ def format_upload(item):
v.pop("timestamp", None)
v.pop("files", None)
v.pop("upload-urls", None)
uploaded_value = v.pop("uploaded", None)
upload_value = v.pop("upload", None)
if upload_value is not None:
msg = "Uploaded" if upload_value else "Skipped, already in server"
if uploaded_value is not None and upload_value is not None:
force_upload = v.pop("force_upload", None)
if force_upload:
msg += " - forced"
if uploaded_value:
msg = "Uploaded"
if force_upload:
msg += " - forced"
elif not upload_value:
msg = "Skipped, already in server"
else:
msg = "Not uploaded"
k = f"{k} ({msg})"
package_sign = v.get("package sign", None)
if package_sign is not None:
if "fail" in package_sign.lower() or "error" in package_sign.lower():
package_sign_error = True
result[k] = format_upload(v)
return result
return item

info = {remote: format_upload(values) for remote, values in info.items()}
if package_sign_error:
results["conan_error"] = f"There were some errors in the signature verification process. " \
"Please check the output."
print_serial(info)


Expand Down Expand Up @@ -97,13 +113,21 @@ def upload(conan_api: ConanAPI, parser, *args):
ref_pattern = ListPattern(args.pattern, package_id="*", only_recipe=args.only_recipe)
package_list = conan_api.list.select(ref_pattern, package_query=args.package_query)

results = {
"conan_api": conan_api
}

if package_list:
# If only if search with "*" we ask for confirmation
if not args.list and not args.confirm and "*" in args.pattern:
package_list = _ask_confirm_upload(conan_api, package_list)

conan_api.upload.upload_full(package_list, remote, enabled_remotes, args.check,
args.force, args.metadata, args.dry_run)
try:
conan_api.upload.upload_full(package_list, remote, enabled_remotes, args.check,
args.force, args.metadata, args.dry_run)
except ConanException as e:
results["conan_error"] = str(e)

elif args.list:
# Don't error on no recipes for automated workflows using list,
# but warn to tell the user that no packages were uploaded
Expand All @@ -113,10 +137,8 @@ def upload(conan_api: ConanAPI, parser, *args):

pkglist = MultiPackagesList()
pkglist.add(remote.name, package_list)
return {
"results": pkglist.serialize(),
"conan_api": conan_api
}
results["results"] = pkglist.serialize()
return results


def _ask_confirm_upload(conan_api, package_list):
Expand Down
6 changes: 4 additions & 2 deletions conan/internal/api/uploader.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def check(self, package_list, remote, force):
def _check_upstream_recipe(self, ref, ref_bundle, remote, force):
output = ConanOutput(scope=str(ref))
output.info("Checking which revisions exist in the remote server")
ref_bundle["uploaded"] = False
try:
assert ref.revision
# TODO: It is a bit ugly, interface-wise to ask for revisions to check existence
Expand All @@ -60,7 +61,7 @@ def _check_upstream_recipe(self, ref, ref_bundle, remote, force):
def _check_upstream_package(self, pref, prev_bundle, remote, force):
assert (pref.revision is not None), "Cannot upload a package without PREV"
assert (pref.ref.revision is not None), "Cannot upload a package without RREV"

prev_bundle["uploaded"] = False
try:
# TODO: It is a bit ugly, interface-wise to ask for revisions to check existence
server_revisions = self._app.remote_manager.get_package_revision(pref, remote)
Expand Down Expand Up @@ -248,7 +249,7 @@ def upload_recipe(self, ref, bundle, remote):

t1 = time.time()
self._app.remote_manager.upload_recipe(ref, cache_files, remote)

bundle["uploaded"] = True
duration = time.time() - t1
output.debug(f"Upload {ref} in {duration} time")
return ref
Expand All @@ -263,6 +264,7 @@ def upload_package(self, pref, prev_bundle, remote):

t1 = time.time()
self._app.remote_manager.upload_package(pref, cache_files, remote)
prev_bundle["uploaded"] = True
duration = time.time() - t1
output.debug(f"Upload {pref} in {duration} time")

Expand Down
Loading
Loading