13
13
import paramiko
14
14
import requests
15
15
import getpass
16
- import shutil
17
16
from natsort import natsorted
18
17
19
18
from sonic_package_manager .database import PackageEntry , PackageDatabase
20
19
from sonic_package_manager .errors import PackageManagerError
21
20
from sonic_package_manager .logger import log
22
21
from sonic_package_manager .manager import PackageManager
23
- from sonic_package_manager .manifest import Manifest , DEFAULT_MANIFEST , MANIFEST_LOCATION , DEFAUT_MANIFEST_NAME , DMFILE_NAME
24
- LOCAL_TARBALL_PATH = "/tmp/local_tarball.gz"
25
- LOCAL_JSON = "/tmp/local_json"
22
+ from sonic_package_manager .manifest import MANIFEST_LOCATION
26
23
27
24
BULLET_UC = '\u2022 '
28
25
@@ -105,8 +102,11 @@ def handle_parse_result(self, ctx, opts, args):
105
102
cls = MutuallyExclusiveOption ,
106
103
mutually_exclusive = ['from_tarball' , 'package_expr' ]),
107
104
click .option ('--from-tarball' ,
108
- type = str ,
109
- help = 'Fetch package from saved image tarball from local/scp/sftp/http' ,
105
+ type = click .Path (exists = True ,
106
+ readable = True ,
107
+ file_okay = True ,
108
+ dir_okay = False ),
109
+ help = 'Fetch package from saved image tarball.' ,
110
110
cls = MutuallyExclusiveOption ,
111
111
mutually_exclusive = ['from_repository' , 'package_expr' ]),
112
112
click .argument ('package-expr' ,
@@ -227,11 +227,6 @@ def manifest(ctx,
227
227
manager : PackageManager = ctx .obj
228
228
229
229
try :
230
- if from_tarball :
231
- #Download the tar file from local/scp/sftp/http
232
- download_file (from_tarball , LOCAL_TARBALL_PATH )
233
- from_tarball = LOCAL_TARBALL_PATH
234
-
235
230
source = manager .get_package_source (package_expr ,
236
231
from_repository ,
237
232
from_tarball )
@@ -272,11 +267,6 @@ def changelog(ctx,
272
267
manager : PackageManager = ctx .obj
273
268
274
269
try :
275
- if from_tarball :
276
- #Download the tar file from local/scp/sftp/http
277
- download_file (from_tarball , LOCAL_TARBALL_PATH )
278
- from_tarball = LOCAL_TARBALL_PATH
279
-
280
270
source = manager .get_package_source (package_expr ,
281
271
from_repository ,
282
272
from_tarball )
@@ -311,52 +301,13 @@ def changelog(ctx,
311
301
def create (ctx , name , from_json ):
312
302
"""Create a new custom local manifest file."""
313
303
314
- #Validation checks
315
304
manager : PackageManager = ctx .obj
316
- if manager .is_installed (name ):
317
- click .echo ("Error: A package with the same name {} is already installed" .format (name ))
318
- return
319
- MFILE_NAME = os .path .join (MANIFEST_LOCATION , name )
320
- if os .path .exists (MFILE_NAME ):
321
- click .echo ("Error: Manifest file '{}' already exists." .format (name ))
322
- return
323
-
324
- #Creation of default manifest file in case the file does not exist
325
- if not os .path .exists (MANIFEST_LOCATION ):
326
- os .mkdir (MANIFEST_LOCATION )
327
- if not os .path .exists (DMFILE_NAME ):
328
- with open (DMFILE_NAME , 'w' ) as file :
329
- json .dump (DEFAULT_MANIFEST , file , indent = 4 )
330
- #click.echo(f"Manifest '{DEFAUT_MANIFEST_NAME}' created now.")
331
-
332
-
333
- #Create the manifest file in centralized location
334
- #Download the json file from scp/sftp/http to local_json_file
335
- try :
336
- if from_json :
337
- download_file (from_json , LOCAL_JSON )
338
- from_json = LOCAL_JSON
339
- data = {}
340
- with open (from_json , 'r' ) as file :
341
- data = json .load (file )
342
- #Validate with manifest scheme
343
- Manifest .marshal (data )
344
-
345
- #Make sure the 'name' is overwritten into the dict
346
- data ['package' ]['name' ] = name
347
- data ['service' ]['name' ] = name
348
-
349
- with open (MFILE_NAME , 'w' ) as file :
350
- json .dump (data , file , indent = 4 )
351
- else :
352
- shutil .copy (DMFILE_NAME , MFILE_NAME )
353
- click .echo (f"Manifest '{ name } ' created successfully." )
305
+ try :
306
+ manager .create_package_manifest (name , from_json )
354
307
except Exception as e :
355
308
click .echo ("Error: Manifest {} creation failed - {}" .format (name , str (e )))
356
309
return
357
310
358
-
359
-
360
311
#At the end of sonic-package-manager install, a new manifest file is created with the name.
361
312
#At the end of sonic-package-manager uninstall name, this manifest file name and name.edit will be deleted.
362
313
#At the end of sonic-package-manager update, we need to mv maniests name.edit to name in case of success, else keep it as such.
@@ -365,67 +316,26 @@ def create(ctx, name, from_json):
365
316
@click .pass_context
366
317
@click .argument ('name' , type = click .Path ())
367
318
@click .option ('--from-json' , type = str , required = True )
368
- #@click.argument('--from-json', type=str, help='Specify Manifest json file')
369
319
@root_privileges_required
370
320
def update (ctx , name , from_json ):
371
321
"""Update an existing custom local manifest file with new one."""
372
322
373
323
manager : PackageManager = ctx .obj
374
- ORG_FILE = os .path .join (MANIFEST_LOCATION , name )
375
- if not os .path .exists (ORG_FILE ):
376
- click .echo (f'Local Manifest file for { name } does not exists to update' )
377
- return
378
324
try :
379
- #download json file from remote/local path
380
- download_file (from_json , LOCAL_JSON )
381
- from_json = LOCAL_JSON
382
- with open (from_json , 'r' ) as file :
383
- data = json .load (file )
384
-
385
- #Validate with manifest scheme
386
- Manifest .marshal (data )
387
-
388
- #Make sure the 'name' is overwritten into the dict
389
- data ['package' ]['name' ] = name
390
- data ['service' ]['name' ] = name
391
-
392
- if manager .is_installed (name ):
393
- edit_name = name + '.edit'
394
- EDIT_FILE = os .path .join (MANIFEST_LOCATION , edit_name )
395
- with open (EDIT_FILE , 'w' ) as edit_file :
396
- json .dump (data , edit_file , indent = 4 )
397
- click .echo (f"Manifest '{ name } ' updated successfully." )
398
- else :
399
- #If package is not installed,
400
- ## update the name file directly
401
- with open (ORG_FILE , 'w' ) as orig_file :
402
- json .dump (data , orig_file , indent = 4 )
403
- click .echo (f"Manifest '{ name } ' updated successfully." )
325
+ manager .update_package_manifest (name , from_json )
404
326
except Exception as e :
405
327
click .echo (f"Error occurred while updating manifest '{ name } ': { e } " )
406
328
return
407
329
408
-
409
330
@manifests .command ('delete' )
410
331
@click .pass_context
411
332
@click .argument ('name' , type = click .Path ())
412
333
@root_privileges_required
413
334
def delete (ctx , name ):
414
335
"""Delete a custom local manifest file."""
415
- # Check if the manifest file exists
416
- mfile_name = "{}{}" .format (MANIFEST_LOCATION , name )
417
- if not os .path .exists (mfile_name ):
418
- click .echo ("Error: Manifest file '{}' not found." .format (name ))
419
- return
420
-
336
+ manager : PackageManager = ctx .obj
421
337
try :
422
- # Confirm deletion with user input
423
- confirm = click .prompt ("Are you sure you want to delete the manifest file '{}'? (y/n)" .format (name ), type = str )
424
- if confirm .lower () == 'y' :
425
- os .remove (mfile_name )
426
- click .echo ("Manifest '{}' deleted successfully." .format (name ))
427
- else :
428
- click .echo ("Deletion cancelled." )
338
+ manager .delete_package_manifest (name )
429
339
except Exception as e :
430
340
click .echo ("Error: Failed to delete manifest file '{}'. {}" .format (name , e ))
431
341
@@ -436,15 +346,9 @@ def delete(ctx, name):
436
346
@root_privileges_required
437
347
def show_manifest (ctx , name ):
438
348
"""Show the contents of custom local manifest file."""
439
- mfile_name = "{}{}" .format (MANIFEST_LOCATION , name )
440
- edit_file_name = "{}.edit" .format (mfile_name )
349
+ manager : PackageManager = ctx .obj
441
350
try :
442
- if os .path .exists (edit_file_name ):
443
- mfile_name = edit_file_name
444
- with open (mfile_name , 'r' ) as file :
445
- data = json .load (file )
446
- click .echo ("Manifest file: {}" .format (name ))
447
- click .echo (json .dumps (data , indent = 4 ))
351
+ manager .show_package_manifest (name )
448
352
except FileNotFoundError :
449
353
click .echo ("Manifest file '{}' not found." .format (name ))
450
354
@@ -453,14 +357,8 @@ def show_manifest(ctx, name):
453
357
@root_privileges_required
454
358
def list_manifests (ctx ):
455
359
"""List all custom local manifest files."""
456
- # Get all files in the manifest location
457
- manifest_files = os .listdir (MANIFEST_LOCATION )
458
- if not manifest_files :
459
- click .echo ("No custom local manifest files found." )
460
- else :
461
- click .echo ("Custom Local Manifest files:" )
462
- for file in manifest_files :
463
- click .echo ("- {}" .format (file ))
360
+ manager : PackageManager = ctx .obj
361
+ manager .list_package_manifest ()
464
362
465
363
466
364
@repository .command ()
@@ -499,78 +397,6 @@ def remove(ctx, name):
499
397
exit_cli (f'Failed to remove repository { name } : { err } ' , fg = 'red' )
500
398
501
399
502
- def parse_url (url ):
503
- # Parse information from URL
504
- parsed_url = urlparse (url )
505
- if parsed_url .scheme == "scp" or parsed_url .scheme == "sftp" :
506
- return parsed_url .username , parsed_url .password , parsed_url .hostname , parsed_url .path
507
- elif parsed_url .scheme == "http" :
508
- return None , None , parsed_url .netloc , parsed_url .path
509
- elif not parsed_url .scheme : # No scheme indicates a local file path
510
- return None , None , None , parsed_url .path
511
- else :
512
- raise ValueError ("Unsupported URL scheme" )
513
-
514
- def validate_url_or_abort (url ):
515
- # Attempt to retrieve HTTP response code
516
- try :
517
- response = requests .head (url )
518
- response_code = response .status_code
519
- except requests .exceptions .RequestException as err :
520
- response_code = None
521
-
522
- if not response_code :
523
- print ("Did not receive a response from remote machine. Aborting..." )
524
- return
525
- else :
526
- # Check for a 4xx response code which indicates a nonexistent URL
527
- if str (response_code ).startswith ('4' ):
528
- print ("Image file not found on remote machine. Aborting..." )
529
- return
530
-
531
- def download_file (url , local_path ):
532
- # Parse information from the URL
533
- username , password , hostname , remote_path = parse_url (url )
534
-
535
- if username is not None :
536
- # If password is not provided, prompt the user for it securely
537
- if password is None :
538
- password = getpass .getpass (prompt = f"Enter password for { username } @{ hostname } : " )
539
-
540
- # Create an SSH client for SCP or SFTP
541
- client = paramiko .SSHClient ()
542
- # Automatically add the server's host key (this is insecure and should be handled differently in production)
543
- client .set_missing_host_key_policy (paramiko .AutoAddPolicy ())
544
-
545
- try :
546
- # Connect to the SSH server
547
- client .connect (hostname , username = username , password = password )
548
-
549
- # Open an SCP channel for SCP or an SFTP channel for SFTP
550
- with client .open_sftp () as sftp :
551
- # Download the file
552
- sftp .get (remote_path , local_path )
553
-
554
- finally :
555
- # Close the SSH connection
556
- client .close ()
557
- elif hostname :
558
- # Download using HTTP for URLs without credentials
559
- validate_url_or_abort (url )
560
- try :
561
- response = requests .get (url )
562
- with open (local_path , 'wb' ) as f :
563
- f .write (response .content )
564
- except requests .exceptions .RequestException as e :
565
- print ("Download error" , e )
566
- return
567
- else :
568
- if os .path .exists (remote_path ):
569
- shutil .copy (remote_path , local_path )
570
- else :
571
- print (f"Error: Source file '{ remote_path } ' does not exist." )
572
-
573
-
574
400
@cli .command ()
575
401
@click .option ('--enable' ,
576
402
is_flag = True ,
@@ -592,7 +418,8 @@ def download_file(url, local_path):
592
418
@click .option ('--use-local-manifest' ,
593
419
is_flag = True ,
594
420
default = None ,
595
- help = 'Use locally created custom manifest file ' )
421
+ help = 'Use locally created custom manifest file. ' ,
422
+ hidden = True )
596
423
@click .option ('--name' ,
597
424
type = str ,
598
425
help = 'custom name for the package' )
@@ -651,11 +478,6 @@ def install(ctx,
651
478
click .echo (f'Local Manifest file for { name } does not exists to install' )
652
479
return
653
480
654
- if from_tarball :
655
- #Download the tar file from local/scp/sftp/http
656
- download_file (from_tarball , LOCAL_TARBALL_PATH )
657
- from_tarball = LOCAL_TARBALL_PATH
658
-
659
481
try :
660
482
manager .install (package_expr ,
661
483
from_repository ,
@@ -671,16 +493,22 @@ def install(ctx,
671
493
672
494
@cli .command ()
673
495
@add_options (PACKAGE_COMMON_OPERATION_OPTIONS )
496
+ @add_options (PACKAGE_COMMON_INSTALL_OPTIONS )
674
497
@click .argument ('name' )
675
498
@click .pass_context
676
499
@root_privileges_required
677
- def update (ctx , name , force , yes ):
678
- """ Update package to the updated manifest file """
500
+ def update (ctx , name , force , yes , skip_host_plugins ):
501
+ """ Update package to the updated manifest file. """
679
502
680
503
manager : PackageManager = ctx .obj
681
504
505
+ update_opts = {
506
+ 'force' : force ,
507
+ 'skip_host_plugins' : skip_host_plugins ,
508
+ 'update_only' : True ,
509
+ }
682
510
try :
683
- manager .update (name , force )
511
+ manager .update (name , ** update_opts )
684
512
except Exception as err :
685
513
exit_cli (f'Failed to update package { name } : { err } ' , fg = 'red' )
686
514
except KeyboardInterrupt :
0 commit comments