44from pulp_glue .common .context import PluginRequirement , PulpEntityContext
55from pulp_glue .common .i18n import get_translation
66from pulp_glue .core .context import PulpArtifactContext
7- from pulp_glue .python .context import PulpPythonContentContext , PulpPythonRepositoryContext
7+ from pulp_glue .python .context import (
8+ PulpPythonContentContext ,
9+ PulpPythonProvenanceContext ,
10+ PulpPythonRepositoryContext ,
11+ )
812
913from pulp_cli .generic import (
1014 PulpCLIContext ,
1418 label_command ,
1519 label_select_option ,
1620 list_command ,
21+ load_json_callback ,
1722 pass_entity_context ,
1823 pass_pulp_context ,
1924 pulp_group ,
2025 pulp_option ,
2126 resource_option ,
2227 show_command ,
28+ type_option ,
2329)
2430
2531translation = get_translation (__package__ )
@@ -37,6 +43,24 @@ def _sha256_artifact_callback(
3743 return value
3844
3945
46+ def _attestation_callback (
47+ ctx : click .Context , param : click .Parameter , value : t .Iterable [str ] | None
48+ ) -> list [t .Any ] | None :
49+ """Callback to process multiple attestation values and combine them into a list."""
50+ if not value :
51+ return None
52+ result = []
53+ for attestation_value in value :
54+ # Use load_json_callback to process each value (supports JSON strings and file paths)
55+ processed = load_json_callback (ctx , param , attestation_value )
56+ # If it's already a list, extend; otherwise append
57+ if isinstance (processed , list ):
58+ result .extend (processed )
59+ else :
60+ result .append (processed )
61+ return result
62+
63+
4064repository_option = resource_option (
4165 "--repository" ,
4266 default_plugin = "python" ,
@@ -51,26 +75,49 @@ def _sha256_artifact_callback(
5175 ),
5276)
5377
78+ package_option = resource_option (
79+ "--package" ,
80+ default_plugin = "python" ,
81+ default_type = "package" ,
82+ lookup_key = "sha256" ,
83+ context_table = {
84+ "python:package" : PulpPythonContentContext ,
85+ },
86+ href_pattern = PulpPythonContentContext .HREF_PATTERN ,
87+ help = _ (
88+ "Package to associate the provenance with in the form"
89+ "'[[<plugin>:]<resource_type>:]<sha256>' or by href/prn."
90+ ),
91+ allowed_with_contexts = (PulpPythonProvenanceContext ,),
92+ required = True ,
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,43 @@ 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" , type = str , allowed_with_contexts = ( PulpPythonContentContext ,) ),
91160 label_select_option ,
92161 ]
93162 )
94163)
95164content .add_command (show_command (decorators = lookup_options ))
96- content .add_command (create_command (decorators = create_options ))
165+ content .add_command (create_command (decorators = create_options + provenance_create_options ))
97166content .add_command (
98167 label_command (
99168 decorators = lookup_options ,
@@ -102,10 +171,21 @@ def content(ctx: click.Context, pulp_ctx: PulpCLIContext, /, content_type: str)
102171)
103172
104173
105- @content .command ()
174+ @content .command (allowed_with_contexts = ( PulpPythonContentContext ,) )
106175@click .option ("--relative-path" , required = True , help = _ ("Exact name of file" ))
107176@click .option ("--file" , type = click .File ("rb" ), required = True , help = _ ("Path to file" ))
108177@chunk_size_option
178+ @pulp_option (
179+ "--attestation" ,
180+ "attestations" ,
181+ multiple = True ,
182+ callback = _attestation_callback ,
183+ needs_plugins = [PluginRequirement ("python" , specifier = ">=3.22.0" )],
184+ help = _ (
185+ "A JSON object containing an attestation for the package. Can be a JSON string or a file"
186+ " path prefixed with '@'. Can be specified multiple times."
187+ ),
188+ )
109189@repository_option
110190@pass_entity_context
111191@pass_pulp_context
@@ -116,12 +196,17 @@ def upload(
116196 relative_path : str ,
117197 file : t .IO [bytes ],
118198 chunk_size : int ,
199+ attestations : list [t .Any ] | None ,
119200 repository : PulpPythonRepositoryContext | None ,
120201) -> None :
121202 """Create a Python package content unit through uploading a file"""
122203 assert isinstance (entity_ctx , PulpPythonContentContext )
123204
124205 result = entity_ctx .upload (
125- relative_path = relative_path , file = file , chunk_size = chunk_size , repository = repository
206+ relative_path = relative_path ,
207+ file = file ,
208+ chunk_size = chunk_size ,
209+ repository = repository ,
210+ attestations = attestations ,
126211 )
127212 pulp_ctx .output_result (result )
0 commit comments