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 ,
@@ -37,6 +42,24 @@ def _sha256_artifact_callback(
3742 return value
3843
3944
45+ def _attestation_callback (
46+ ctx : click .Context , param : click .Parameter , value : t .Iterable [str ] | None
47+ ) -> list [t .Any ] | None :
48+ """Callback to process multiple attestation values and combine them into a list."""
49+ if not value :
50+ return None
51+ result = []
52+ for attestation_value in value :
53+ # Use load_json_callback to process each value (supports JSON strings and file paths)
54+ processed = load_json_callback (ctx , param , attestation_value )
55+ # If it's already a list, extend; otherwise append
56+ if isinstance (processed , list ):
57+ result .extend (processed )
58+ else :
59+ result .append (processed )
60+ return result
61+
62+
4063repository_option = resource_option (
4164 "--repository" ,
4265 default_plugin = "python" ,
@@ -51,26 +74,50 @@ def _sha256_artifact_callback(
5174 ),
5275)
5376
77+ package_option = resource_option (
78+ "--package" ,
79+ default_plugin = "python" ,
80+ default_type = "package" ,
81+ lookup_key = "sha256" ,
82+ context_table = {
83+ "python:package" : PulpPythonContentContext ,
84+ },
85+ href_pattern = PulpPythonContentContext .HREF_PATTERN ,
86+ help = _ (
87+ "Package to associate the provenance with in the form"
88+ "'[[<plugin>:]<resource_type>:]<sha256>' or by href/prn."
89+ ),
90+ allowed_with_contexts = (PulpPythonProvenanceContext ,),
91+ required = True ,
92+ )
93+
5494
5595@pulp_group ()
5696@click .option (
5797 "-t" ,
5898 "--type" ,
5999 "content_type" ,
60- type = click .Choice (["package" ], case_sensitive = False ),
100+ type = click .Choice (["package" , "provenance" ], case_sensitive = False ),
61101 default = "package" ,
62102)
63103@pass_pulp_context
64104@click .pass_context
65105def content (ctx : click .Context , pulp_ctx : PulpCLIContext , / , content_type : str ) -> None :
66106 if content_type == "package" :
67107 ctx .obj = PulpPythonContentContext (pulp_ctx )
108+ elif content_type == "provenance" :
109+ ctx .obj = PulpPythonProvenanceContext (pulp_ctx )
68110 else :
69111 raise NotImplementedError ()
70112
71113
72114create_options = [
73- click .option ("--relative-path" , required = True , help = _ ("Exact name of file" )),
115+ pulp_option (
116+ "--relative-path" ,
117+ required = True ,
118+ help = _ ("Exact name of file" ),
119+ allowed_with_contexts = (PulpPythonContentContext ,),
120+ ),
74121 click .option (
75122 "--sha256" ,
76123 "artifact" ,
@@ -79,21 +126,49 @@ 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+ pulp_option (
147+ "--file" ,
148+ type = click .File ("rb" ),
149+ help = _ ("Provenance JSON file" ),
150+ allowed_with_contexts = (PulpPythonProvenanceContext ,),
151+ ),
152+ package_option ,
153+ pulp_option (
154+ "--verify/--no-verify" ,
155+ default = True ,
156+ needs_plugins = [PluginRequirement ("python" , specifier = ">=3.22.0" )],
157+ help = _ ("Verify the provenance" ),
158+ allowed_with_contexts = (PulpPythonProvenanceContext ,),
159+ ),
85160]
86161lookup_options = [href_option ]
87162content .add_command (
88163 list_command (
89164 decorators = [
90- click . option ("--filename" , type = str ),
165+ pulp_option ("--filename" , type = str , allowed_with_contexts = ( PulpPythonContentContext ,) ),
91166 label_select_option ,
92167 ]
93168 )
94169)
95170content .add_command (show_command (decorators = lookup_options ))
96- content .add_command (create_command (decorators = create_options ))
171+ content .add_command (create_command (decorators = create_options + provenance_create_options ))
97172content .add_command (
98173 label_command (
99174 decorators = lookup_options ,
@@ -102,10 +177,21 @@ def content(ctx: click.Context, pulp_ctx: PulpCLIContext, /, content_type: str)
102177)
103178
104179
105- @content .command ()
180+ @content .command (allowed_with_contexts = ( PulpPythonContentContext ,) )
106181@click .option ("--relative-path" , required = True , help = _ ("Exact name of file" ))
107182@click .option ("--file" , type = click .File ("rb" ), required = True , help = _ ("Path to file" ))
108183@chunk_size_option
184+ @pulp_option (
185+ "--attestation" ,
186+ "attestations" ,
187+ multiple = True ,
188+ callback = _attestation_callback ,
189+ needs_plugins = [PluginRequirement ("python" , specifier = ">=3.22.0" )],
190+ help = _ (
191+ "A JSON object containing an attestation for the package. Can be a JSON string or a file"
192+ " path prefixed with '@'. Can be specified multiple times."
193+ ),
194+ )
109195@repository_option
110196@pass_entity_context
111197@pass_pulp_context
@@ -116,12 +202,17 @@ def upload(
116202 relative_path : str ,
117203 file : t .IO [bytes ],
118204 chunk_size : int ,
205+ attestations : list [t .Any ] | None ,
119206 repository : PulpPythonRepositoryContext | None ,
120207) -> None :
121208 """Create a Python package content unit through uploading a file"""
122209 assert isinstance (entity_ctx , PulpPythonContentContext )
123210
124211 result = entity_ctx .upload (
125- relative_path = relative_path , file = file , chunk_size = chunk_size , repository = repository
212+ relative_path = relative_path ,
213+ file = file ,
214+ chunk_size = chunk_size ,
215+ repository = repository ,
216+ attestations = attestations ,
126217 )
127218 pulp_ctx .output_result (result )
0 commit comments