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,44 @@ 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+ ),
74116 click .option (
75117 "--sha256" ,
76118 "artifact" ,
@@ -79,21 +121,49 @@ def content(ctx: click.Context, pulp_ctx: PulpCLIContext, /, content_type: str)
79121 ),
80122 pulp_option (
81123 "--file-url" ,
82- help = _ ("Remote url to download and create python content from" ),
124+ help = _ ("Remote url to download and create {entity} from" ),
83125 needs_plugins = [PluginRequirement ("core" , specifier = ">=3.56.1" )],
84126 ),
127+ pulp_option (
128+ "--attestation" ,
129+ "attestations" ,
130+ multiple = True ,
131+ callback = _attestation_callback ,
132+ needs_plugins = [PluginRequirement ("python" , specifier = ">=3.22.0" )],
133+ help = _ (
134+ "A JSON object containing an attestation for the package. Can be a JSON string or a "
135+ "file path prefixed with '@'. Can be specified multiple times."
136+ ),
137+ allowed_with_contexts = (PulpPythonContentContext ,),
138+ ),
139+ ]
140+ provenance_create_options = [
141+ pulp_option (
142+ "--file" ,
143+ type = click .File ("rb" ),
144+ help = _ ("Provenance JSON file" ),
145+ allowed_with_contexts = (PulpPythonProvenanceContext ,),
146+ ),
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