99import tempfile
1010from os import PathLike
1111from pathlib import Path
12- from typing import Mapping , Optional , Sequence , TypeAlias
12+ from typing import Mapping , Optional , Sequence , TypeAlias , IO
1313
1414LOGGER = logging .getLogger ("operator-cert" )
1515
@@ -118,18 +118,13 @@ def run_playbook(
118118 run (* command , cwd = self .path )
119119
120120
121- class Podman :
121+ class RegistryAuthMixin :
122122 """
123- Utility class to interact with Podman.
123+ Mixin class to help running the tools in the podman family (podman, buildah, skopeo)
124+ with a given set of authentication credentials for the container registries
124125 """
125126
126127 def __init__ (self , auth : Optional [Mapping [str , tuple [str , str ]]] = None ):
127- """
128- Initialize the Podman instance
129-
130- Args:
131- auth: The authentication credentials for registries
132- """
133128 self ._auth = {
134129 "auths" : {
135130 registry : {
@@ -142,26 +137,52 @@ def __init__(self, auth: Optional[Mapping[str, tuple[str, str]]] = None):
142137 }
143138 }
144139
145- def _run (self , * args : CommandArg ) -> None :
140+ def save_auth (self , dest_file : IO [str ]) -> None :
141+ """
142+ Dump the auth credentials to a json file
143+ Args:
144+ dest_file: destination json file
145+ """
146+ json .dump (self ._auth , dest_file )
147+
148+ def run (self , * command : CommandArg ) -> None :
149+ """
150+ Run the given command with the REGISTRY_AUTH_FILE environment variable pointing
151+ to a temporary file containing a json representation of the credentials in a format
152+ compatible with podman, buildah and skopeo
153+ Args:
154+ *command: command line to execute
155+ """
156+
157+ if self ._auth ["auths" ]:
158+ with tempfile .NamedTemporaryFile (
159+ mode = "w" ,
160+ encoding = "utf-8" ,
161+ suffix = ".json" ,
162+ delete = True ,
163+ delete_on_close = False ,
164+ ) as tmp :
165+ self .save_auth (tmp )
166+ tmp .close ()
167+ LOGGER .debug ("Using auth file: %s" , tmp .name )
168+ run (* command , env = {"REGISTRY_AUTH_FILE" : tmp .name })
169+ else :
170+ run (* command )
171+
172+
173+ class Podman (RegistryAuthMixin ):
174+ """
175+ Utility class to interact with Podman.
176+ """
177+
178+ def run (self , * args : CommandArg ) -> None :
146179 """
147180 Run a podman subcommand
148181
149182 Args:
150183 *args: The podman subcommand and its arguments
151184 """
152- command : list [CommandArg ] = ["podman" ]
153- command .extend (args )
154- with tempfile .NamedTemporaryFile (
155- mode = "w" ,
156- encoding = "utf-8" ,
157- suffix = ".json" ,
158- delete = True ,
159- delete_on_close = False ,
160- ) as tmp :
161- json .dump (self ._auth , tmp )
162- tmp .close ()
163- LOGGER .debug ("Using podman auth file: %s" , tmp .name )
164- run (* command , env = {"REGISTRY_AUTH_FILE" : tmp .name })
185+ super ().run ("podman" , * args )
165186
166187 def build (
167188 self ,
@@ -185,7 +206,7 @@ def build(
185206 command .extend (["-f" , containerfile ])
186207 if extra_args :
187208 command .extend (extra_args )
188- self ._run (* command )
209+ self .run (* command )
189210
190211 def push (self , image : str ) -> None :
191212 """
@@ -194,4 +215,52 @@ def push(self, image: str) -> None:
194215 Args:
195216 image: The name of the image to push.
196217 """
197- self ._run ("push" , image )
218+ self .run ("push" , image )
219+
220+
221+ class Skopeo (RegistryAuthMixin ):
222+ """
223+ Utility class to interact with Skopeo.
224+ """
225+
226+ def run (self , * args : CommandArg ) -> None :
227+ """
228+ Run a skopeo subcommand
229+
230+ Args:
231+ *args: The skopeo subcommand and its arguments
232+ """
233+ super ().run ("skopeo" , * args )
234+
235+ def copy (
236+ self ,
237+ from_image : str ,
238+ to_image : str ,
239+ extra_args : Optional [Sequence [CommandArg ]] = None ,
240+ ) -> None :
241+ """
242+ Copy a container image
243+
244+ Args:
245+ from_image: source container image ref
246+ to_image: destination image ref
247+ extra_args: optional args to add to the skopeo command line
248+ """
249+
250+ command : list [CommandArg ] = ["copy" , from_image , to_image ]
251+ if extra_args :
252+ command .extend (extra_args )
253+ self .run (* command )
254+
255+ def delete (
256+ self ,
257+ image : str ,
258+ ) -> None :
259+ """
260+ Delete a container image
261+
262+ Args:
263+ image: container image ref
264+ """
265+
266+ self .run ("delete" , image )
0 commit comments