1+ import json
12import typing as t
23
34import click
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,48 @@ def _sha256_artifact_callback(
5176 ),
5277)
5378
79+ package_option = resource_option (
80+ "--package" ,
81+ default_plugin = "python" ,
82+ default_type = "packages" ,
83+ lookup_key = "sha256" ,
84+ context_table = {
85+ "python:packages" : 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+ )
94+
5495
5596@pulp_group ()
56- @click . option (
57- "-t" ,
58- "--type" ,
59- "content_type" ,
60- type = click . Choice ([ "package" ], case_sensitive = False ) ,
97+ @type_option (
98+ choices = {
99+ "package" : PulpPythonContentContext ,
100+ "provenance" : PulpPythonProvenanceContext ,
101+ } ,
61102 default = "package" ,
103+ case_sensitive = False ,
62104)
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 ()
105+ def content () -> None :
106+ pass
70107
71108
72109create_options = [
73- click .option ("--relative-path" , required = True , help = _ ("Exact name of file" )),
110+ pulp_option (
111+ "--relative-path" ,
112+ required = True ,
113+ help = _ ("Exact name of file" ),
114+ allowed_with_contexts = (PulpPythonContentContext ,),
115+ ),
116+ pulp_option (
117+ "--file" ,
118+ type = click .File ("rb" ),
119+ help = _ ("Path to the file to create {entity} from" ),
120+ ),
74121 click .option (
75122 "--sha256" ,
76123 "artifact" ,
@@ -79,21 +126,45 @@ def content(ctx: click.Context, pulp_ctx: PulpCLIContext, /, content_type: str)
79126 ),
80127 pulp_option (
81128 "--file-url" ,
82- help = _ ("Remote url to download and create python content from" ),
129+ help = _ ("Remote url to download and create {entity} from" ),
83130 needs_plugins = [PluginRequirement ("core" , specifier = ">=3.56.1" )],
84131 ),
132+ pulp_option (
133+ "--attestation" ,
134+ "attestations" ,
135+ multiple = True ,
136+ callback = _attestation_callback ,
137+ needs_plugins = [PluginRequirement ("python" , specifier = ">=3.22.0" )],
138+ help = _ (
139+ "A JSON object containing an attestation for the package. Can be a JSON string or a "
140+ "file path prefixed with '@'. Can be specified multiple times."
141+ ),
142+ allowed_with_contexts = (PulpPythonContentContext ,),
143+ ),
144+ ]
145+ provenance_create_options = [
146+ package_option ,
147+ pulp_option (
148+ "--verify/--no-verify" ,
149+ default = True ,
150+ needs_plugins = [PluginRequirement ("python" , specifier = ">=3.22.0" )],
151+ help = _ ("Verify the provenance" ),
152+ allowed_with_contexts = (PulpPythonProvenanceContext ,),
153+ ),
85154]
86155lookup_options = [href_option ]
87156content .add_command (
88157 list_command (
89158 decorators = [
90- click .option ("--filename" , type = str ),
159+ pulp_option ("--filename" , allowed_with_contexts = (PulpPythonContentContext ,)),
160+ pulp_option ("--sha256" ),
91161 label_select_option ,
162+ package_option ,
92163 ]
93164 )
94165)
95166content .add_command (show_command (decorators = lookup_options ))
96- content .add_command (create_command (decorators = create_options ))
167+ content .add_command (create_command (decorators = create_options + provenance_create_options ))
97168content .add_command (
98169 label_command (
99170 decorators = lookup_options ,
@@ -102,10 +173,21 @@ def content(ctx: click.Context, pulp_ctx: PulpCLIContext, /, content_type: str)
102173)
103174
104175
105- @content .command ()
176+ @content .command (allowed_with_contexts = ( PulpPythonContentContext ,) )
106177@click .option ("--relative-path" , required = True , help = _ ("Exact name of file" ))
107178@click .option ("--file" , type = click .File ("rb" ), required = True , help = _ ("Path to file" ))
108179@chunk_size_option
180+ @pulp_option (
181+ "--attestation" ,
182+ "attestations" ,
183+ multiple = True ,
184+ callback = _attestation_callback ,
185+ needs_plugins = [PluginRequirement ("python" , specifier = ">=3.22.0" )],
186+ help = _ (
187+ "A JSON object containing an attestation for the package. Can be a JSON string or a file"
188+ " path prefixed with '@'. Can be specified multiple times."
189+ ),
190+ )
109191@repository_option
110192@pass_entity_context
111193@pass_pulp_context
@@ -116,12 +198,17 @@ def upload(
116198 relative_path : str ,
117199 file : t .IO [bytes ],
118200 chunk_size : int ,
201+ attestations : list [t .Any ] | None ,
119202 repository : PulpPythonRepositoryContext | None ,
120203) -> None :
121204 """Create a Python package content unit through uploading a file"""
122205 assert isinstance (entity_ctx , PulpPythonContentContext )
123206
124207 result = entity_ctx .upload (
125- relative_path = relative_path , file = file , chunk_size = chunk_size , repository = repository
208+ relative_path = relative_path ,
209+ file = file ,
210+ chunk_size = chunk_size ,
211+ repository = repository ,
212+ attestations = attestations ,
126213 )
127214 pulp_ctx .output_result (result )
0 commit comments