11import typing as t
22
33import click
4+ import json
45from pulp_glue .common .context import PluginRequirement , PulpEntityContext
56from pulp_glue .common .i18n import get_translation
67from pulp_glue .core .context import PulpArtifactContext
7- from pulp_glue .python .context import PulpPythonContentContext , PulpPythonRepositoryContext
8+ from pulp_glue .python .context import (
9+ PulpPythonContentContext ,
10+ PulpPythonProvenanceContext ,
11+ PulpPythonRepositoryContext ,
12+ )
813
914from pulp_cli .generic import (
1015 PulpCLIContext ,
1419 label_command ,
1520 label_select_option ,
1621 list_command ,
22+ load_json_callback ,
1723 pass_entity_context ,
1824 pass_pulp_context ,
1925 pulp_group ,
2026 pulp_option ,
2127 resource_option ,
2228 show_command ,
29+ type_option ,
2330)
2431
2532translation = get_translation (__package__ )
@@ -37,6 +44,24 @@ def _sha256_artifact_callback(
3744 return value
3845
3946
47+ def _attestation_callback (
48+ ctx : click .Context , param : click .Parameter , value : t .Iterable [str ] | None
49+ ) -> str | None :
50+ """Callback to process multiple attestation values and combine them into a list."""
51+ if not value :
52+ return None
53+ result = []
54+ for attestation_value in value :
55+ # Use load_json_callback to process each value (supports JSON strings and file paths)
56+ processed = load_json_callback (ctx , param , attestation_value )
57+ # If it's already a list, extend; otherwise append
58+ if isinstance (processed , list ):
59+ result .extend (processed )
60+ else :
61+ result .append (processed )
62+ return json .dumps (result )
63+
64+
4065repository_option = resource_option (
4166 "--repository" ,
4267 default_plugin = "python" ,
@@ -51,26 +76,49 @@ def _sha256_artifact_callback(
5176 ),
5277)
5378
79+ package_option = resource_option (
80+ "--package" ,
81+ default_plugin = "python" ,
82+ default_type = "package" ,
83+ lookup_key = "sha256" ,
84+ context_table = {
85+ "python:package" : PulpPythonContentContext ,
86+ },
87+ href_pattern = PulpPythonContentContext .HREF_PATTERN ,
88+ help = _ (
89+ "Package to associate the provenance with in the form"
90+ "'[[<plugin>:]<resource_type>:]<sha256>' or by href/prn."
91+ ),
92+ allowed_with_contexts = (PulpPythonProvenanceContext ,),
93+ required = True ,
94+ )
95+
5496
5597@pulp_group ()
56- @click . option (
57- "-t" ,
58- "--type" ,
59- "content_type" ,
60- type = click . Choice ([ "package" ], case_sensitive = False ) ,
98+ @type_option (
99+ choices = {
100+ "package" : PulpPythonContentContext ,
101+ "provenance" : PulpPythonProvenanceContext ,
102+ } ,
61103 default = "package" ,
104+ case_sensitive = False ,
62105)
63- @pass_pulp_context
64- @click .pass_context
65- def content (ctx : click .Context , pulp_ctx : PulpCLIContext , / , content_type : str ) -> None :
66- if content_type == "package" :
67- ctx .obj = PulpPythonContentContext (pulp_ctx )
68- else :
69- raise NotImplementedError ()
106+ def content () -> None :
107+ pass
70108
71109
72110create_options = [
73- click .option ("--relative-path" , required = True , help = _ ("Exact name of file" )),
111+ pulp_option (
112+ "--relative-path" ,
113+ required = True ,
114+ help = _ ("Exact name of file" ),
115+ allowed_with_contexts = (PulpPythonContentContext ,),
116+ ),
117+ pulp_option (
118+ "--file" ,
119+ type = click .File ("rb" ),
120+ help = _ ("Path to the file to create {entity} from" ),
121+ ),
74122 click .option (
75123 "--sha256" ,
76124 "artifact" ,
@@ -79,21 +127,43 @@ def content(ctx: click.Context, pulp_ctx: PulpCLIContext, /, content_type: str)
79127 ),
80128 pulp_option (
81129 "--file-url" ,
82- help = _ ("Remote url to download and create python content from" ),
130+ help = _ ("Remote url to download and create {entity} from" ),
83131 needs_plugins = [PluginRequirement ("core" , specifier = ">=3.56.1" )],
84132 ),
133+ pulp_option (
134+ "--attestation" ,
135+ "attestations" ,
136+ multiple = True ,
137+ callback = _attestation_callback ,
138+ needs_plugins = [PluginRequirement ("python" , specifier = ">=3.22.0" )],
139+ help = _ (
140+ "A JSON object containing an attestation for the package. Can be a JSON string or a "
141+ "file path prefixed with '@'. Can be specified multiple times."
142+ ),
143+ allowed_with_contexts = (PulpPythonContentContext ,),
144+ ),
145+ ]
146+ provenance_create_options = [
147+ package_option ,
148+ pulp_option (
149+ "--verify/--no-verify" ,
150+ default = True ,
151+ needs_plugins = [PluginRequirement ("python" , specifier = ">=3.22.0" )],
152+ help = _ ("Verify the provenance" ),
153+ allowed_with_contexts = (PulpPythonProvenanceContext ,),
154+ ),
85155]
86156lookup_options = [href_option ]
87157content .add_command (
88158 list_command (
89159 decorators = [
90- click . option ("--filename" , type = str ),
160+ pulp_option ("--filename" , type = str , allowed_with_contexts = ( PulpPythonContentContext ,) ),
91161 label_select_option ,
92162 ]
93163 )
94164)
95165content .add_command (show_command (decorators = lookup_options ))
96- content .add_command (create_command (decorators = create_options ))
166+ content .add_command (create_command (decorators = create_options + provenance_create_options ))
97167content .add_command (
98168 label_command (
99169 decorators = lookup_options ,
@@ -102,10 +172,21 @@ def content(ctx: click.Context, pulp_ctx: PulpCLIContext, /, content_type: str)
102172)
103173
104174
105- @content .command ()
175+ @content .command (allowed_with_contexts = ( PulpPythonContentContext ,) )
106176@click .option ("--relative-path" , required = True , help = _ ("Exact name of file" ))
107177@click .option ("--file" , type = click .File ("rb" ), required = True , help = _ ("Path to file" ))
108178@chunk_size_option
179+ @pulp_option (
180+ "--attestation" ,
181+ "attestations" ,
182+ multiple = True ,
183+ callback = _attestation_callback ,
184+ needs_plugins = [PluginRequirement ("python" , specifier = ">=3.22.0" )],
185+ help = _ (
186+ "A JSON object containing an attestation for the package. Can be a JSON string or a file"
187+ " path prefixed with '@'. Can be specified multiple times."
188+ ),
189+ )
109190@repository_option
110191@pass_entity_context
111192@pass_pulp_context
@@ -116,12 +197,17 @@ def upload(
116197 relative_path : str ,
117198 file : t .IO [bytes ],
118199 chunk_size : int ,
200+ attestations : list [t .Any ] | None ,
119201 repository : PulpPythonRepositoryContext | None ,
120202) -> None :
121203 """Create a Python package content unit through uploading a file"""
122204 assert isinstance (entity_ctx , PulpPythonContentContext )
123205
124206 result = entity_ctx .upload (
125- relative_path = relative_path , file = file , chunk_size = chunk_size , repository = repository
207+ relative_path = relative_path ,
208+ file = file ,
209+ chunk_size = chunk_size ,
210+ repository = repository ,
211+ attestations = attestations ,
126212 )
127213 pulp_ctx .output_result (result )
0 commit comments