diff --git a/CHANGES/+python-attestation.feature b/CHANGES/+python-attestation.feature new file mode 100644 index 000000000..278e9ed29 --- /dev/null +++ b/CHANGES/+python-attestation.feature @@ -0,0 +1,3 @@ +Added support for uploading attestations with Python Package content. +Added support for uploading Python Provenance content. +Added support for specifying syncing of Python Provenance content. diff --git a/pulp-glue/pulp_glue/python/context.py b/pulp-glue/pulp_glue/python/context.py index 38eec9fcb..bc66c2e33 100644 --- a/pulp-glue/pulp_glue/python/context.py +++ b/pulp-glue/pulp_glue/python/context.py @@ -40,6 +40,16 @@ class PulpPythonContentContext(PulpContentContext): CAPABILITIES = {"upload": []} +class PulpPythonProvenanceContext(PulpContentContext): + PLUGIN = "python" + RESOURCE_TYPE = "provenance" + ENTITY = _("python provenance") + ENTITIES = _("python provenances") + HREF = "python_python_provenance_content_href" + ID_PREFIX = "content_python_provenance" + NEEDS_PLUGINS = [PluginRequirement("python", specifier=">=3.22.0")] + + class PulpPythonDistributionContext(PulpDistributionContext): PLUGIN = "python" RESOURCE_TYPE = "python" diff --git a/pulpcore/cli/python/content.py b/pulpcore/cli/python/content.py index a7d05e916..b57491424 100644 --- a/pulpcore/cli/python/content.py +++ b/pulpcore/cli/python/content.py @@ -4,7 +4,11 @@ from pulp_glue.common.context import PluginRequirement, PulpEntityContext from pulp_glue.common.i18n import get_translation from pulp_glue.core.context import PulpArtifactContext -from pulp_glue.python.context import PulpPythonContentContext, PulpPythonRepositoryContext +from pulp_glue.python.context import ( + PulpPythonContentContext, + PulpPythonProvenanceContext, + PulpPythonRepositoryContext, +) from pulp_cli.generic import ( PulpCLIContext, @@ -14,6 +18,7 @@ label_command, label_select_option, list_command, + load_json_callback, pass_entity_context, pass_pulp_context, pulp_group, @@ -37,6 +42,24 @@ def _sha256_artifact_callback( return value +def _attestation_callback( + ctx: click.Context, param: click.Parameter, value: t.Iterable[str] | None +) -> list[t.Any] | None: + """Callback to process multiple attestation values and combine them into a list.""" + if not value: + return None + result = [] + for attestation_value in value: + # Use load_json_callback to process each value (supports JSON strings and file paths) + processed = load_json_callback(ctx, param, attestation_value) + # If it's already a list, extend; otherwise append + if isinstance(processed, list): + result.extend(processed) + else: + result.append(processed) + return result + + repository_option = resource_option( "--repository", default_plugin="python", @@ -51,13 +74,30 @@ def _sha256_artifact_callback( ), ) +package_option = resource_option( + "--package", + default_plugin="python", + default_type="package", + lookup_key="sha256", + context_table={ + "python:package": PulpPythonContentContext, + }, + href_pattern=PulpPythonContentContext.HREF_PATTERN, + help=_( + "Package to associate the provenance with in the form" + "'[[:]:]' or by href/prn." + ), + allowed_with_contexts=(PulpPythonProvenanceContext,), + required=True, +) + @pulp_group() @click.option( "-t", "--type", "content_type", - type=click.Choice(["package"], case_sensitive=False), + type=click.Choice(["package", "provenance"], case_sensitive=False), default="package", ) @pass_pulp_context @@ -65,12 +105,19 @@ def _sha256_artifact_callback( def content(ctx: click.Context, pulp_ctx: PulpCLIContext, /, content_type: str) -> None: if content_type == "package": ctx.obj = PulpPythonContentContext(pulp_ctx) + elif content_type == "provenance": + ctx.obj = PulpPythonProvenanceContext(pulp_ctx) else: raise NotImplementedError() create_options = [ - click.option("--relative-path", required=True, help=_("Exact name of file")), + pulp_option( + "--relative-path", + required=True, + help=_("Exact name of file"), + allowed_with_contexts=(PulpPythonContentContext,), + ), click.option( "--sha256", "artifact", @@ -79,21 +126,49 @@ def content(ctx: click.Context, pulp_ctx: PulpCLIContext, /, content_type: str) ), pulp_option( "--file-url", - help=_("Remote url to download and create python content from"), + help=_("Remote url to download and create {entity} from"), needs_plugins=[PluginRequirement("core", specifier=">=3.56.1")], ), + pulp_option( + "--attestation", + "attestations", + multiple=True, + callback=_attestation_callback, + needs_plugins=[PluginRequirement("python", specifier=">=3.22.0")], + help=_( + "A JSON object containing an attestation for the package. Can be a JSON string or a " + "file path prefixed with '@'. Can be specified multiple times." + ), + allowed_with_contexts=(PulpPythonContentContext,), + ), +] +provenance_create_options = [ + pulp_option( + "--file", + type=click.File("rb"), + help=_("Provenance JSON file"), + allowed_with_contexts=(PulpPythonProvenanceContext,), + ), + package_option, + pulp_option( + "--verify/--no-verify", + default=True, + needs_plugins=[PluginRequirement("python", specifier=">=3.22.0")], + help=_("Verify the provenance"), + allowed_with_contexts=(PulpPythonProvenanceContext,), + ), ] lookup_options = [href_option] content.add_command( list_command( decorators=[ - click.option("--filename", type=str), + pulp_option("--filename", type=str, allowed_with_contexts=(PulpPythonContentContext,)), label_select_option, ] ) ) content.add_command(show_command(decorators=lookup_options)) -content.add_command(create_command(decorators=create_options)) +content.add_command(create_command(decorators=create_options + provenance_create_options)) content.add_command( label_command( decorators=lookup_options, @@ -102,10 +177,21 @@ def content(ctx: click.Context, pulp_ctx: PulpCLIContext, /, content_type: str) ) -@content.command() +@content.command(allowed_with_contexts=(PulpPythonContentContext,)) @click.option("--relative-path", required=True, help=_("Exact name of file")) @click.option("--file", type=click.File("rb"), required=True, help=_("Path to file")) @chunk_size_option +@pulp_option( + "--attestation", + "attestations", + multiple=True, + callback=_attestation_callback, + needs_plugins=[PluginRequirement("python", specifier=">=3.22.0")], + help=_( + "A JSON object containing an attestation for the package. Can be a JSON string or a file" + " path prefixed with '@'. Can be specified multiple times." + ), +) @repository_option @pass_entity_context @pass_pulp_context @@ -116,12 +202,17 @@ def upload( relative_path: str, file: t.IO[bytes], chunk_size: int, + attestations: list[t.Any] | None, repository: PulpPythonRepositoryContext | None, ) -> None: """Create a Python package content unit through uploading a file""" assert isinstance(entity_ctx, PulpPythonContentContext) result = entity_ctx.upload( - relative_path=relative_path, file=file, chunk_size=chunk_size, repository=repository + relative_path=relative_path, + file=file, + chunk_size=chunk_size, + repository=repository, + attestations=attestations, ) pulp_ctx.output_result(result) diff --git a/pulpcore/cli/python/remote.py b/pulpcore/cli/python/remote.py index 187657a2c..9bbc96a22 100644 --- a/pulpcore/cli/python/remote.py +++ b/pulpcore/cli/python/remote.py @@ -98,6 +98,12 @@ def remote(ctx: click.Context, pulp_ctx: PulpCLIContext, /, remote_type: str) -> callback=load_json_callback, needs_plugins=[PluginRequirement("python", specifier=">=3.2.0")], ), + pulp_option( + "--provenance", + type=click.BOOL, + help=_("Sync available package provenances"), + needs_plugins=[PluginRequirement("python", specifier=">=3.22.0")], + ), ] remote.add_command(list_command(decorators=remote_filter_options)) diff --git a/pulpcore/cli/python/repository.py b/pulpcore/cli/python/repository.py index 671d4ef07..a81ef81f2 100644 --- a/pulpcore/cli/python/repository.py +++ b/pulpcore/cli/python/repository.py @@ -11,22 +11,21 @@ from pulp_glue.common.i18n import get_translation from pulp_glue.python.context import ( PulpPythonContentContext, + PulpPythonProvenanceContext, PulpPythonRemoteContext, PulpPythonRepositoryContext, ) from pulp_cli.generic import ( - GroupOption, PulpCLIContext, create_command, create_content_json_callback, destroy_command, href_option, - json_callback, label_command, label_select_option, list_command, - load_file_wrapper, + lookup_callback, name_option, pass_pulp_context, pass_repository_context, @@ -60,31 +59,7 @@ ) -def _content_callback(ctx: click.Context, param: click.Parameter, value: t.Any) -> t.Any: - if value: - pulp_ctx = ctx.find_object(PulpCLIContext) - assert pulp_ctx is not None - ctx.obj = PulpPythonContentContext(pulp_ctx, entity=value) - return value - - -CONTENT_LIST_SCHEMA = s.Schema([{"sha256": str, "filename": s.And(str, len)}]) - - -@load_file_wrapper -def _content_list_callback(ctx: click.Context, param: click.Parameter, value: str | None) -> t.Any: - if value is None: - return None - - result = json_callback(ctx, param, value) - try: - return CONTENT_LIST_SCHEMA.validate(result) - except s.SchemaError as e: - raise click.ClickException( - _("Validation of '{parameter}' failed: {error}").format( - parameter=param.name, error=str(e) - ) - ) +CONTENT_LIST_SCHEMA = s.Schema([{"sha256": str}]) @pulp_group() @@ -119,36 +94,32 @@ def repository(ctx: click.Context, pulp_ctx: PulpCLIContext, /, repo_type: str) ] create_options = update_options + [click.option("--name", required=True)] package_options = [ - click.option("--sha256", cls=GroupOption, expose_value=False, group=["filename"]), - click.option( - "--filename", - callback=_content_callback, + pulp_option( + "--sha256", + callback=lookup_callback("sha256"), expose_value=False, - cls=GroupOption, - group=["sha256"], - help=_("Filename of the python package."), + help=_("SHA256 digest of the {entity}."), ), + href_option, ] -content_json_callback = create_content_json_callback( - PulpPythonContentContext, schema=CONTENT_LIST_SCHEMA -) +content_json_callback = create_content_json_callback(None, schema=CONTENT_LIST_SCHEMA) modify_options = [ - click.option( + pulp_option( "--add-content", callback=content_json_callback, help=_( - """JSON string with a list of objects to add to the repository. - Each object must contain the following keys: "sha256", "filename". - The argument prefixed with the '@' can be the path to a JSON file with a list of objects.""" + """JSON string with a list of {entities} to add to the repository. + Each {entity} must contain the following keys: "sha256". + The argument prefixed with the '@' can be the path to a JSON file with a list of {entities}.""" ), ), - click.option( + pulp_option( "--remove-content", callback=content_json_callback, help=_( - """JSON string with a list of objects to remove from the repository. - Each object must contain the following keys: "sha256", "filename". - The argument prefixed with the '@' can be the path to a JSON file with a list of objects.""" + """JSON string with a list of {entities} to remove from the repository. + Each {entity} must contain the following keys: "sha256". + The argument prefixed with the '@' can be the path to a JSON file with a list of {entities}.""" ), ), ] @@ -163,7 +134,10 @@ def repository(ctx: click.Context, pulp_ctx: PulpCLIContext, /, repo_type: str) repository.add_command(label_command(decorators=nested_lookup_options)) repository.add_command( repository_content_command( - contexts={"package": PulpPythonContentContext}, + contexts={ + "package": PulpPythonContentContext, + "provenance": PulpPythonProvenanceContext, + }, add_decorators=package_options, remove_decorators=package_options, modify_decorators=modify_options, diff --git a/tests/scripts/pulp_python/shelf-reader-0.1.tar.gz.publish.attestation b/tests/scripts/pulp_python/shelf-reader-0.1.tar.gz.publish.attestation new file mode 100644 index 000000000..151bd27d2 --- /dev/null +++ b/tests/scripts/pulp_python/shelf-reader-0.1.tar.gz.publish.attestation @@ -0,0 +1 @@ +{"version":1,"verification_material":{"certificate":"MIIIMzCCB7igAwIBAgIUKrdo9OfFJy6MuUewIeE/ILWw/tswCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUxMjA0MjAzMjM5WhcNMjUxMjA0MjA0MjM5WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEleubASHneG8xQmJ/HacfPp63RpBRb71lTUW3uqc/dxFttAkIu11LtR5Y1aRa7h/uFP6e9B/3WAV11xiu29vxRqOCBtcwggbTMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUcDHutpRx316lThqpRMpz2kw+/f4wHwYDVR0jBBgwFoAUcYYwphR8Ym/599b0BRp/X//rb6wwgaUGA1UdEQEB/wSBmjCBl4aBlGh0dHBzOi8vZ2l0aHViLmNvbS9zaWdzdG9yZS1jb25mb3JtYW5jZS9leHRyZW1lbHktZGFuZ2Vyb3VzLXB1YmxpYy1vaWRjLWJlYWNvbi8uZ2l0aHViL3dvcmtmbG93cy9leHRyZW1lbHktZGFuZ2Vyb3VzLW9pZGMtYmVhY29uLnltbEByZWZzL2hlYWRzL21haW4wOQYKKwYBBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbTAfBgorBgEEAYO/MAECBBF3b3JrZmxvd19kaXNwYXRjaDA2BgorBgEEAYO/MAEDBCg3OTU4NmMxZGVhZDhiODhmYmZhOWE3ODgyZGExMTZkZWNmNjc0YTQyMC0GCisGAQQBg78wAQQEH0V4dHJlbWVseSBkYW5nZXJvdXMgT0lEQyBiZWFjb24wSQYKKwYBBAGDvzABBQQ7c2lnc3RvcmUtY29uZm9ybWFuY2UvZXh0cmVtZWx5LWRhbmdlcm91cy1wdWJsaWMtb2lkYy1iZWFjb24wHQYKKwYBBAGDvzABBgQPcmVmcy9oZWFkcy9tYWluMDsGCisGAQQBg78wAQgELQwraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbTCBpgYKKwYBBAGDvzABCQSBlwyBlGh0dHBzOi8vZ2l0aHViLmNvbS9zaWdzdG9yZS1jb25mb3JtYW5jZS9leHRyZW1lbHktZGFuZ2Vyb3VzLXB1YmxpYy1vaWRjLWJlYWNvbi8uZ2l0aHViL3dvcmtmbG93cy9leHRyZW1lbHktZGFuZ2Vyb3VzLW9pZGMtYmVhY29uLnltbEByZWZzL2hlYWRzL21haW4wOAYKKwYBBAGDvzABCgQqDCg3OTU4NmMxZGVhZDhiODhmYmZhOWE3ODgyZGExMTZkZWNmNjc0YTQyMB0GCisGAQQBg78wAQsEDwwNZ2l0aHViLWhvc3RlZDBeBgorBgEEAYO/MAEMBFAMTmh0dHBzOi8vZ2l0aHViLmNvbS9zaWdzdG9yZS1jb25mb3JtYW5jZS9leHRyZW1lbHktZGFuZ2Vyb3VzLXB1YmxpYy1vaWRjLWJlYWNvbjA4BgorBgEEAYO/MAENBCoMKDc5NTg2YzFkZWFkOGI4OGZiZmE5YTc4ODJkYTExNmRlY2Y2NzRhNDIwHwYKKwYBBAGDvzABDgQRDA9yZWZzL2hlYWRzL21haW4wGQYKKwYBBAGDvzABDwQLDAk2MzI1OTY4OTcwNwYKKwYBBAGDvzABEAQpDCdodHRwczovL2dpdGh1Yi5jb20vc2lnc3RvcmUtY29uZm9ybWFuY2UwGQYKKwYBBAGDvzABEQQLDAkxMzE4MDQ1NjMwgaYGCisGAQQBg78wARIEgZcMgZRodHRwczovL2dpdGh1Yi5jb20vc2lnc3RvcmUtY29uZm9ybWFuY2UvZXh0cmVtZWx5LWRhbmdlcm91cy1wdWJsaWMtb2lkYy1iZWFjb24vLmdpdGh1Yi93b3JrZmxvd3MvZXh0cmVtZWx5LWRhbmdlcm91cy1vaWRjLWJlYWNvbi55bWxAcmVmcy9oZWFkcy9tYWluMDgGCisGAQQBg78wARMEKgwoNzk1ODZjMWRlYWQ4Yjg4ZmJmYTlhNzg4MmRhMTE2ZGVjZjY3NGE0MjAhBgorBgEEAYO/MAEUBBMMEXdvcmtmbG93X2Rpc3BhdGNoMIGCBgorBgEEAYO/MAEVBHQMcmh0dHBzOi8vZ2l0aHViLmNvbS9zaWdzdG9yZS1jb25mb3JtYW5jZS9leHRyZW1lbHktZGFuZ2Vyb3VzLXB1YmxpYy1vaWRjLWJlYWNvbi9hY3Rpb25zL3J1bnMvMTk5NDMwMzAxOTkvYXR0ZW1wdHMvMTAWBgorBgEEAYO/MAEWBAgMBnB1YmxpYzCBigYKKwYBBAHWeQIEAgR8BHoAeAB2ACswvNxoiMni4dgmKV50H0g5MZYC8pwzy15DQP6yrIZ6AAABmusRH1wAAAQDAEcwRQIgMjrB10EwMTKAWmjTMab2AX1YMg1lWJp7xV3yhBx7GyYCIQC5zr44ZzUSPnJ3Gtm6k8M1CBoIzDYWzwBIoRAdh5NE3TAKBggqhkjOPQQDAwNpADBmAjEAgyfhSQno+h3UugP0A6V0A8b++q1IV4hYm4dVfi6skZKkEYMFbs0ocMWqWlJg8PaFAjEAt7+HDiWOfa3MxLPnIdzeEPz4kbgXZqKQDMHY5m1ZG976bnJBwttMAbAEXhELfyHl","transparency_entries":[{"logIndex":"51125016","logId":{"keyId":"0y8wo8MtY5wrdiIFohx7sHeI5oKDpK5vQhGHI6G+pJY="},"kindVersion":{"kind":"dsse","version":"0.0.1"},"integratedTime":"1764880360","inclusionPromise":{"signedEntryTimestamp":"MEUCIQC48Y/s6Rv+IoXoftvGOhM3sccchkODD/YqwIKJgwJAiAIgWkva5KEaCl3P78T21oElOTbq9cOxurz//9N9giFPDWw="},"inclusionProof":{"logIndex":"19442604","rootHash":"EdmMTYTkzJL0XRtufY9DMHS2axO0Vg6jO4eVM/Cetd4=","treeSize":"19442605","hashes":["k5UsnMaUY0kOQlFg0Za5deoskYSOUTFQ2Jzl3upcbX0=","F8+CGCoMNj9Jb1Csuk4bxbAWW35iktydoLKPPLk6Hb8=","P6IFMwnZlp99wiunjusnNW+K5V06iOEtzfoD9MtHCSU=","V4//f/ISXnSeJjUckmcLLzwS38XjXwMjrMAw6B3hKfg=","baLnSFGPpXZDhE38YKkGPW9P35RXQTaZlgDJ2i/u5xA=","YPKEUUZB3AVufkBQm5r7wLJeJMECETlxWoHo3+sKnHE=","VEcYk6UKGgyx1fCjyU6cfCPViC8OZFBjvHz8fco4eGM=","tf738kZ3YKyYiskKppTF5etATmFZgyY6H1ktyC0leCA=","gXQKVsGeO2E9oDbviE7+SuHFG5g7UH35dYGHc9JSz8s=","6kC2DcsZfB5F/TmenKHUzDr2UH6XK//Ch6hC8PZWJ5c=","JA9P8Q8v916uA/xomSrsiXeVBiyxxQAD5Mer9MBIYzA=","BrjYsYvo65dTDWKO8aY0s+9WaWHlVdZuOEzsBHLHe8U="],"checkpoint":{"envelope":"rekor.sigstage.dev - 8202293616175992157\n19442605\nEdmMTYTkzJL0XRtufY9DMHS2axO0Vg6jO4eVM/Cetd4=\n\n— rekor.sigstage.dev 0y8wozBFAiAPcLZnrw7rn8sFhxqGjp5q58EtMUibZptS8W1Mk6CcAAIhAJ6c+pCBCCe4+K1+zxuUsofR9Y56J5pTBvqvfGceJI/l\n"}},"canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiYzJkZjczOTEzMzgyZmVkM2MzNTU5ODRjOTdiN2Y2NTc5YTYxZmVmZDAwOWMxYmM3M2M2ODE0ODk3ZjMxNDY3MyJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6IjRkMjdjMTZjMTI3ZDM4ZGE2NDU3MGVmMmY3Mjc2MjIwOWVjMzlkMmU0NjhiYjQ0YTU5NjE1MjhlOTMyNDNjNWQifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVZQ0lRQ2hSUGN1N2w5QXBIbTAxNlE3bnpob3pxaHkvYXVBV1NiWWc5MUpxQzluYlFJaEFOdGtFV01hV2RHS0dUZHVNZW4yQ081eDhPY1N0UStyazlnWVFWeDk1YU9VIiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VsTmVrTkRRamRwWjBGM1NVSkJaMGxWUzNKa2J6bFBaa1pLZVRaTmRWVmxkMGxsUlM5SlRGZDNMM1J6ZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwVmVFMXFRVEJOYWtGNlRXcE5OVmRvWTA1TmFsVjRUV3BCTUUxcVFUQk5hazAxVjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVnNaWFZpUVZOSWJtVkhPSGhSYlVvdlNHRmpabEJ3TmpOU2NFSlNZamN4YkZSVlZ6TUtkWEZqTDJSNFJuUjBRV3RKZFRFeFRIUlNOVmt4WVZKaE4yZ3ZkVVpRTm1VNVFpOHpWMEZXTVRGNGFYVXlPWFo0VW5GUFEwSjBZM2RuWjJKVVRVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVmpSRWgxQ25Sd1VuZ3pNVFpzVkdoeGNGSk5jSG95YTNjckwyWTBkMGgzV1VSV1VqQnFRa0puZDBadlFWVmpXVmwzY0doU09GbHRMelU1T1dJd1FsSndMMWd2TDNJS1lqWjNkMmRoVlVkQk1WVmtSVkZGUWk5M1UwSnRha05DYkRSaFFteEhhREJrU0VKNlQyazRkbG95YkRCaFNGWnBURzFPZG1KVE9YcGhWMlI2WkVjNWVRcGFVekZxWWpJMWJXSXpTblJaVnpWcVdsTTViR1ZJVW5sYVZ6RnNZa2hyZEZwSFJuVmFNbFo1WWpOV2VreFlRakZaYlhod1dYa3hkbUZYVW1wTVYwcHNDbGxYVG5aaWFUaDFXakpzTUdGSVZtbE1NMlIyWTIxMGJXSkhPVE5qZVRsc1pVaFNlVnBYTVd4aVNHdDBXa2RHZFZveVZubGlNMVo2VEZjNWNGcEhUWFFLV1cxV2FGa3lPWFZNYm14MFlrVkNlVnBYV25wTU1taHNXVmRTZWt3eU1XaGhWelIzVDFGWlMwdDNXVUpDUVVkRWRucEJRa0ZSVVhKaFNGSXdZMGhOTmdwTWVUa3dZakowYkdKcE5XaFpNMUp3WWpJMWVreHRaSEJrUjJneFdXNVdlbHBZU21waU1qVXdXbGMxTUV4dFRuWmlWRUZtUW1kdmNrSm5SVVZCV1U4dkNrMUJSVU5DUWtZellqTktjbHB0ZUhaa01UbHJZVmhPZDFsWVVtcGhSRUV5UW1kdmNrSm5SVVZCV1U4dlRVRkZSRUpEWnpOUFZGVTBUbTFOZUZwSFZtZ0tXa1JvYVU5RWFHMVpiVnBvVDFkRk0wOUVaM2xhUjBWNFRWUmFhMXBYVG0xT2FtTXdXVlJSZVUxRE1FZERhWE5IUVZGUlFtYzNPSGRCVVZGRlNEQldOQXBrU0Vwc1lsZFdjMlZUUW10WlZ6VnVXbGhLZG1SWVRXZFVNR3hGVVhsQ2FWcFhSbXBpTWpSM1UxRlpTMHQzV1VKQ1FVZEVkbnBCUWtKUlVUZGpNbXh1Q21NelVuWmpiVlYwV1RJNWRWcHRPWGxpVjBaMVdUSlZkbHBZYURCamJWWjBXbGQ0TlV4WFVtaGliV1JzWTIwNU1XTjVNWGRrVjBwellWZE5kR0l5YkdzS1dYa3hhVnBYUm1waU1qUjNTRkZaUzB0M1dVSkNRVWRFZG5wQlFrSm5VVkJqYlZadFkzazViMXBYUm10amVUbDBXVmRzZFUxRWMwZERhWE5IUVZGUlFncG5OemgzUVZGblJVeFJkM0poU0ZJd1kwaE5Oa3g1T1RCaU1uUnNZbWsxYUZrelVuQmlNalY2VEcxa2NHUkhhREZaYmxaNldsaEthbUl5TlRCYVZ6VXdDa3h0VG5aaVZFTkNjR2RaUzB0M1dVSkNRVWRFZG5wQlFrTlJVMEpzZDNsQ2JFZG9NR1JJUW5wUGFUaDJXakpzTUdGSVZtbE1iVTUyWWxNNWVtRlhaSG9LWkVjNWVWcFRNV3BpTWpWdFlqTktkRmxYTldwYVV6bHNaVWhTZVZwWE1XeGlTR3QwV2tkR2RWb3lWbmxpTTFaNlRGaENNVmx0ZUhCWmVURjJZVmRTYWdwTVYwcHNXVmRPZG1KcE9IVmFNbXd3WVVoV2FVd3paSFpqYlhSdFlrYzVNMk41T1d4bFNGSjVXbGN4YkdKSWEzUmFSMFoxV2pKV2VXSXpWbnBNVnpsd0NscEhUWFJaYlZab1dUSTVkVXh1YkhSaVJVSjVXbGRhZWt3eWFHeFpWMUo2VERJeGFHRlhOSGRQUVZsTFMzZFpRa0pCUjBSMmVrRkNRMmRSY1VSRFp6TUtUMVJWTkU1dFRYaGFSMVpvV2tSb2FVOUVhRzFaYlZwb1QxZEZNMDlFWjNsYVIwVjRUVlJhYTFwWFRtMU9hbU13V1ZSUmVVMUNNRWREYVhOSFFWRlJRZ3BuTnpoM1FWRnpSVVIzZDA1YU1td3dZVWhXYVV4WGFIWmpNMUpzV2tSQ1pVSm5iM0pDWjBWRlFWbFBMMDFCUlUxQ1JrRk5WRzFvTUdSSVFucFBhVGgyQ2xveWJEQmhTRlpwVEcxT2RtSlRPWHBoVjJSNlpFYzVlVnBUTVdwaU1qVnRZak5LZEZsWE5XcGFVemxzWlVoU2VWcFhNV3hpU0d0MFdrZEdkVm95Vm5rS1lqTldla3hZUWpGWmJYaHdXWGt4ZG1GWFVtcE1WMHBzV1ZkT2RtSnFRVFJDWjI5eVFtZEZSVUZaVHk5TlFVVk9Ra052VFV0RVl6Vk9WR2N5V1hwR2F3cGFWMFpyVDBkSk5FOUhXbWxhYlVVMVdWUmpORTlFU210WlZFVjRUbTFTYkZreVdUSk9lbEpvVGtSSmQwaDNXVXRMZDFsQ1FrRkhSSFo2UVVKRVoxRlNDa1JCT1hsYVYxcDZUREpvYkZsWFVucE1NakZvWVZjMGQwZFJXVXRMZDFsQ1FrRkhSSFo2UVVKRWQxRk1SRUZyTWsxNlNURlBWRmswVDFSamQwNTNXVXNLUzNkWlFrSkJSMFIyZWtGQ1JVRlJjRVJEWkc5a1NGSjNZM3B2ZGt3eVpIQmtSMmd4V1drMWFtSXlNSFpqTW14dVl6TlNkbU50VlhSWk1qbDFXbTA1ZVFwaVYwWjFXVEpWZDBkUldVdExkMWxDUWtGSFJIWjZRVUpGVVZGTVJFRnJlRTE2UlRSTlJGRXhUbXBOZDJkaFdVZERhWE5IUVZGUlFtYzNPSGRCVWtsRkNtZGFZMDFuV2xKdlpFaFNkMk42YjNaTU1tUndaRWRvTVZscE5XcGlNakIyWXpKc2JtTXpVblpqYlZWMFdUSTVkVnB0T1hsaVYwWjFXVEpWZGxwWWFEQUtZMjFXZEZwWGVEVk1WMUpvWW0xa2JHTnRPVEZqZVRGM1pGZEtjMkZYVFhSaU1teHJXWGt4YVZwWFJtcGlNalIyVEcxa2NHUkhhREZaYVRrellqTktjZ3BhYlhoMlpETk5kbHBZYURCamJWWjBXbGQ0TlV4WFVtaGliV1JzWTIwNU1XTjVNWFpoVjFKcVRGZEtiRmxYVG5aaWFUVTFZbGQ0UVdOdFZtMWplVGx2Q2xwWFJtdGplVGwwV1Zkc2RVMUVaMGREYVhOSFFWRlJRbWMzT0hkQlVrMUZTMmQzYjA1NmF6RlBSRnBxVFZkU2JGbFhVVFJaYW1jMFdtMUtiVmxVYkdnS1RucG5ORTF0VW1oTlZFVXlXa2RXYWxwcVdUTk9SMFV3VFdwQmFFSm5iM0pDWjBWRlFWbFBMMDFCUlZWQ1FrMU5SVmhrZG1OdGRHMWlSemt6V0RKU2NBcGpNMEpvWkVkT2IwMUpSME5DWjI5eVFtZEZSVUZaVHk5TlFVVldRa2hSVFdOdGFEQmtTRUo2VDJrNGRsb3liREJoU0ZacFRHMU9kbUpUT1hwaFYyUjZDbVJIT1hsYVV6RnFZakkxYldJelNuUlpWelZxV2xNNWJHVklVbmxhVnpGc1lraHJkRnBIUm5WYU1sWjVZak5XZWt4WVFqRlpiWGh3V1hreGRtRlhVbW9LVEZkS2JGbFhUblppYVRsb1dUTlNjR0l5TlhwTU0wb3hZbTVOZGsxVWF6Vk9SRTEzVFhwQmVFOVVhM1paV0ZJd1dsY3hkMlJJVFhaTlZFRlhRbWR2Y2dwQ1owVkZRVmxQTDAxQlJWZENRV2ROUW01Q01WbHRlSEJaZWtOQ2FXZFpTMHQzV1VKQ1FVaFhaVkZKUlVGblVqaENTRzlCWlVGQ01rRkRjM2QyVG5odkNtbE5ibWswWkdkdFMxWTFNRWd3WnpWTldsbERPSEIzZW5reE5VUlJVRFo1Y2tsYU5rRkJRVUp0ZFhOU1NERjNRVUZCVVVSQlJXTjNVbEZKWjAxcWNrSUtNVEJGZDAxVVMwRlhiV3BVVFdGaU1rRllNVmxOWnpGc1YwcHdOM2hXTTNsb1FuZzNSM2xaUTBsUlF6VjZjalEwV25wVlUxQnVTak5IZEcwMmF6aE5NUXBEUW05SmVrUlpWM3AzUWtsdlVrRmthRFZPUlROVVFVdENaMmR4YUd0cVQxQlJVVVJCZDA1d1FVUkNiVUZxUlVGbmVXWm9VMUZ1Ynl0b00xVjFaMUF3Q2tFMlZqQkJPR0lySzNFeFNWWTBhRmx0TkdSV1ptazJjMnRhUzJ0RldVMUdZbk13YjJOTlYzRlhiRXBuT0ZCaFJrRnFSVUYwTnl0SVJHbFhUMlpoTTAwS2VFeFFia2xrZW1WRlVIbzBhMkpuV0ZweFMxRkVUVWhaTlcweFdrYzVOelppYmtwQ2QzUjBUVUZpUVVWWWFFVk1abmxJYkFvdExTMHRMVVZPUkNCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2c9PSJ9XX19"}]},"envelope":{"statement":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoic2hlbGYtcmVhZGVyLTAuMS50YXIuZ3oiLCJkaWdlc3QiOnsic2hhMjU2IjoiMDRjZmQ4YmI0Zjg0M2UzNWQ1MWJmZGVmMjAzNTEwOWJkZWE4MzFiNTVhNTdjM2U2YTE1NGQxNGJlMTE2Mzk4YyJ9fV0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2RvY3MucHlwaS5vcmcvYXR0ZXN0YXRpb25zL3B1Ymxpc2gvdjEiLCJwcmVkaWNhdGUiOm51bGx9","signature":"MEYCIQChRPcu7l9ApHm016Q7nzhozqhy/auAWSbYg91JqC9nbQIhANtkEWMaWdGKGTduMen2CO5x8OcStQ+rk9gYQVx95aOU"}} \ No newline at end of file diff --git a/tests/scripts/pulp_python/test_content.sh b/tests/scripts/pulp_python/test_content.sh index d9d31b00c..200144196 100755 --- a/tests/scripts/pulp_python/test_content.sh +++ b/tests/scripts/pulp_python/test_content.sh @@ -37,3 +37,13 @@ expect_succ pulp python repository content add --repository "cli_test_python_rep expect_succ pulp python repository content add --repository "$HREF" --sha256 "$sha256" --filename "shelf-reader-0.1.tar.gz" --base-version 0 expect_succ pulp python repository content remove --repository "cli_test_python_repository" --sha256 "$sha256" --filename "shelf-reader-0.1.tar.gz" expect_succ pulp python repository content remove --repository "$HREF" --sha256 "$sha256" --filename "shelf-reader-0.1.tar.gz" --base-version 1 + +if pulp debug has-plugin --name "python" --specifier ">=3.22.0" +then + attestation_file="$(dirname "$(realpath "$0")")"/shelf-reader-0.1.tar.gz.publish.attestation + expect_succ pulp python content create --file "shelf-reader-0.1.tar.gz" --relative-path "shelf-reader-0.1.tar.gz" --attestation "@$attestation_file" + expect_succ pulp python content --type provenance list --package "$content_href" + provenance_href="$(echo "$OUTPUT" | tr '\r\n' ' ' | jq -r .[0].pulp_href)" + expect_succ pulp python content --type provenance show --href "$provenance_href" + expect_succ pulp python repository content --type provenance add --repository "cli_test_python_repository" --href "$provenance_href" +fi \ No newline at end of file diff --git a/tests/scripts/pulp_python/test_remote.sh b/tests/scripts/pulp_python/test_remote.sh index fb346e7b6..943afd94d 100755 --- a/tests/scripts/pulp_python/test_remote.sh +++ b/tests/scripts/pulp_python/test_remote.sh @@ -38,4 +38,8 @@ test "$(echo "$OUTPUT" | jq -r .includes[2])" = "pulp_python" expect_succ pulp python remote list expect_succ pulp python remote create --name "cli_test_complex_remote" --url "$PYTHON_REMOTE_URL" --keep-latest-packages 3 --package-types '["sdist", "bdist_wheel"]' --exclude-platforms '["windows"]' +if pulp debug has-plugin --name "python" --specifier ">=3.22.0" +then + expect_succ pulp python remote update --remote "cli_test_complex_remote" --provenance "true" +fi expect_succ pulp python remote destroy --remote "cli_test_python_remote"