11import argparse
22import asyncio
33import json
4- import os
54import sys
65from getpass import getpass
76
87from rich import print
8+ from rich .table import Table
99
1010from crpy .common import HTTPConnectionError , UnauthorizedError
1111from crpy .registry import RegistryInfo
12- from crpy .storage import save_credentials
12+ from crpy .storage import (
13+ decode_credentials ,
14+ get_config ,
15+ remove_credentials ,
16+ save_credentials ,
17+ )
1318
1419
1520async def _pull (args ):
16- ri = RegistryInfo .from_url (args .url [0 ])
21+ ri = RegistryInfo .from_url (args .url [0 ], proxy = args . proxy , insecure = args . insecure )
1722 filename = args .filename
1823 if not filename :
1924 # make file name compatible
2025 filename = ri .repository .replace (":" , "_" ).replace ("/" , "_" )
21- await ri .pull (filename )
26+ await ri .pull (filename , args . architecture [ 0 ] if args . architecture else None )
2227
2328
2429async def _push (args ):
25- ri = RegistryInfo .from_url (args .url [0 ])
30+ ri = RegistryInfo .from_url (args .url [0 ], proxy = args . proxy , insecure = args . insecure )
2631 await ri .push (args .filename [0 ])
2732
2833
@@ -31,19 +36,38 @@ async def _login(args):
3136 args .username = input ("Username: " )
3237 if args .password is None :
3338 args .password = getpass ("Password: " )
34- ri = RegistryInfo .from_url (args .url )
39+ ri = RegistryInfo .from_url (args .url , proxy = args . proxy , insecure = args . insecure )
3540 await ri .auth (username = args .username , password = args .password )
3641 save_credentials (ri .registry , args .username , args .password )
3742
3843
44+ async def _logout (args ):
45+ ri = RegistryInfo .from_url (args .url )
46+ assert not ri .repository , (
47+ "Invalid url provided. Please provide the full registry url, without repository name.\n "
48+ " Example: [bold]index.docker.io[/bold] instead of [bold]index.docker.io/library/alpine[/bold]\n "
49+ " [bold]http://localhost:5000[/bold] instead of [bold]localhost:5000[/bold]"
50+ )
51+ if remove_credentials (ri .registry ):
52+ print (f"Removed credentials for { ri .registry } " )
53+ else :
54+ raise ValueError (f"Could find find credentials for { ri .registry } " )
55+
56+
3957async def _inspect_manifest (args ):
40- ri = RegistryInfo .from_url (args .url [0 ])
41- manifest = await ri .get_manifest_from_architecture ()
58+ ri = RegistryInfo .from_url (args .url [0 ], proxy = args .proxy , insecure = args .insecure )
59+ if args .fat and args .architecture :
60+ raise ValueError ("Cannot provide --fat and --architecture together." )
61+ if args .fat :
62+ manifest_raw = await ri .get_manifest (fat = True )
63+ manifest = manifest_raw .json ()
64+ else :
65+ manifest = await ri .get_manifest_from_architecture (args .architecture [0 ] if args .architecture else None )
4266 print (manifest )
4367
4468
4569async def _inspect_config (args ):
46- ri = RegistryInfo .from_url (args .url [0 ])
70+ ri = RegistryInfo .from_url (args .url [0 ], proxy = args . proxy , insecure = args . insecure )
4771 raw_config = await ri .get_config ()
4872 config = json .loads (raw_config .data )
4973 if not args .short :
@@ -54,7 +78,7 @@ async def _inspect_config(args):
5478
5579
5680async def _inspect_layer (args ):
57- ri = RegistryInfo .from_url (args .url [0 ])
81+ ri = RegistryInfo .from_url (args .url [0 ], proxy = args . proxy , insecure = args . insecure )
5882 layers = await ri .get_layers ()
5983 ref = args .layer_reference [0 ]
6084 try :
@@ -69,27 +93,43 @@ async def _inspect_layer(args):
6993
7094
7195async def _repositories (args ):
72- ri = RegistryInfo .from_url (args .url [0 ])
96+ ri = RegistryInfo .from_url (args .url [0 ], proxy = args . proxy , insecure = args . insecure )
7397 for entry in await ri .list_repositories ():
7498 print (entry )
7599
76100
77101async def _tags (args ):
78- ri = RegistryInfo .from_url (args .url [0 ])
102+ ri = RegistryInfo .from_url (args .url [0 ], proxy = args . proxy , insecure = args . insecure )
79103 if not ri .repository :
80104 raise ValueError ("Repository must be provided to list tags!" )
81105 for entry in await ri .list_tags ():
82106 print (entry )
83107
84108
85109async def _delete (args ):
86- ri = RegistryInfo .from_url (args .url [0 ])
110+ ri = RegistryInfo .from_url (args .url [0 ], proxy = args . proxy , insecure = args . insecure )
87111 if not ri .repository :
88112 raise ValueError ("Repository must be provided to list tags!" )
89113 r = await ri .delete_tag ()
90114 print (r .data )
91115
92116
117+ async def _auth (args ):
118+ config = get_config ()
119+
120+ table = Table (title = "Saved credentials" , title_style = "bold" )
121+ table .add_column ("Index" , style = "blue" )
122+ table .add_column ("Url" , style = "cyan" , no_wrap = True )
123+ table .add_column ("Username" , style = "magenta" )
124+ table .add_column ("Password" , style = "green" )
125+ for idx , (url , entry ) in enumerate (config ["auths" ].items ()):
126+ username , password = decode_credentials (entry ["auth" ])
127+ if not args .show_passwords :
128+ password = f"{ password [0 :2 ]} ***{ password [- 2 :]} "
129+ table .add_row (str (idx ), url , username , password )
130+ print (table )
131+
132+
93133def main (* args ):
94134 parser = argparse .ArgumentParser (
95135 prog = "crpy" ,
@@ -102,15 +142,31 @@ def main(*args):
102142 "--insecure" ,
103143 action = "store_true" ,
104144 help = "Use insecure registry. Ignores the validation of the certificate (useful for development registries)." ,
145+ default = False ,
146+ )
147+ parser .add_argument (
148+ "-p" ,
149+ "--proxy" ,
150+ nargs = 1 ,
151+ help = "Proxy for all requests. If your proxy contains authentication, pass it on the request in the usual "
152+ 'format "http://user:[email protected] "' ,
105153 default = None ,
106154 )
107- parser .add_argument ("-p" , "--proxy" , nargs = 1 , help = "Proxy for all requests." , default = None )
108155 subparsers = parser .add_subparsers ()
109156 pull = subparsers .add_parser (
110157 "pull" ,
111158 help = "Pulls a docker image from a remove repo." ,
112159 )
113160 pull .set_defaults (func = _pull )
161+ pull .add_argument (
162+ "--architecture" ,
163+ "-a" ,
164+ "--arch" ,
165+ "--platform" ,
166+ nargs = 1 ,
167+ help = "Architecture for the to be pulled." ,
168+ default = None ,
169+ )
114170 pull .add_argument ("url" , nargs = 1 , help = "Remote repository to pull from." )
115171 pull .add_argument ("filename" , nargs = "?" , help = "Output file for the compressed image." )
116172
@@ -122,6 +178,7 @@ def main(*args):
122178 push .add_argument ("filename" , nargs = 1 , help = "File containing the docker image to be pushed." )
123179 push .add_argument ("url" , nargs = 1 , help = "Remote repository to push to." )
124180
181+ # authentication
125182 login = subparsers .add_parser ("login" , help = "Logs in on a remote repo" )
126183 login .set_defaults (func = _login )
127184 login .add_argument (
@@ -133,29 +190,53 @@ def main(*args):
133190 login .add_argument ("--username" , "-u" , nargs = "?" , help = "Username" , default = None )
134191 login .add_argument ("--password" , "-p" , nargs = "?" , help = "Password" , default = None )
135192
136- inspect = subparsers .add_parser (
137- "inspect" ,
138- help = "Inspects a docker registry metadata. It can inspect configs, manifests and layers." ,
193+ logout = subparsers .add_parser ("logout" , help = "Logs out of a remote repo" )
194+ logout .add_argument ("url" , nargs = "?" , help = "Remote repository to logout from." , default = "index.docker.io" )
195+ logout .set_defaults (func = _logout )
196+
197+ auth = subparsers .add_parser ("auth" , help = "Shows authenticated repositories" )
198+ auth .add_argument (
199+ "--show-passwords" ,
200+ "-s" ,
201+ action = "store_true" ,
202+ default = False ,
203+ help = "If the password or token should be shown in clear text." ,
139204 )
140- inspect_subparser = inspect .add_subparsers ()
205+ auth .set_defaults (func = _auth )
206+
141207 # manifest
142- manifest = inspect_subparser .add_parser ("manifest" , help = "Inspects a docker registry metadata." )
208+ manifest = subparsers .add_parser ("manifest" , help = "Inspects a docker registry metadata." )
209+ manifest .add_argument (
210+ "--fat" ,
211+ "-f" ,
212+ action = "store_true" ,
213+ help = "If should retrieve the fat manifest, with all different architechtures ." ,
214+ )
215+ manifest .add_argument (
216+ "--architecture" ,
217+ "-a" ,
218+ "--arch" ,
219+ "--platform" ,
220+ nargs = 1 ,
221+ help = "Architecture to retrieve the manifest for." ,
222+ default = None ,
223+ )
143224 manifest .add_argument ("url" , nargs = 1 , help = "Remote repository url." )
144225 manifest .set_defaults (func = _inspect_manifest )
145226 # config
146- config = inspect_subparser .add_parser ("config" , help = "Inspects a docker registry metadata." )
227+ config = subparsers .add_parser ("config" , help = "Inspects a docker registry metadata." )
147228 config .add_argument ("url" , nargs = 1 , help = "Remote repository url." )
148229 config .set_defaults (func = _inspect_config , short = False )
149230 # commands
150- commands = inspect_subparser .add_parser (
231+ commands = subparsers .add_parser (
151232 "commands" ,
152233 help = "Inspects a docker registry build commands. "
153234 "These are the same as when you check individual image layers on Docker hub." ,
154235 )
155236 commands .add_argument ("url" , nargs = 1 , help = "Remote repository url." )
156237 commands .set_defaults (func = _inspect_config , short = True )
157238 # layer
158- layer = inspect_subparser .add_parser ("layer" , help = "Inspects a docker registry layer." )
239+ layer = subparsers .add_parser ("layer" , help = "Inspects a docker registry layer." )
159240 layer .add_argument ("url" , nargs = 1 , help = "Remote repository url." )
160241 layer .add_argument (
161242 "layer_reference" ,
@@ -182,10 +263,6 @@ def main(*args):
182263
183264 arguments = parser .parse_args (args if args else None )
184265
185- # if a proxy is set, use it on env variables
186- if arguments .proxy :
187- os .environ ["HTTP_PROXY" ] = os .environ ["HTTPS_PROXY" ] = arguments .proxy
188-
189266 try :
190267 if not hasattr (arguments , "func" ):
191268 parser .print_help ()
0 commit comments