Skip to content

Commit 550816b

Browse files
authored
ThirdPartyContainerManagement(TPCM)_in_SonicPackageManager (#2815)
ThirdPartyContainerManagement(TPCM) support in SonicPackageManager allows third party dockers to be installed on the sonic system. The Manifest file is generated from a custom local default file. The Manifest file could be updated through "sonic-package-manager manifests update" command and later the running package could be updated with the new manifest file through "sonic-package-manager update" #### What I did There are many Third Party application dockers, that can be used in SONiC to provision, manage and monitor SONiC devices. The dockers need not be compatible with SONiC, but can almost work independently with minimal SONiC interfaces. These are extensions to SONiC and require additional capabilities to seamlessly integrate with SONiC. These are related to installation, upgrade, and configuration. This change is an enhancement to the SONiC Application Extension Infrastructure to enable integrating a Third Party Application in the form of dockers with SONiC. Moreover, the process of downloading image tarballs for the dockers (packages) supports SCP, SFTP, and URL before installing them. #### How I did it The Sonic-package-manager framework has been enhanced to support ThirdPartyContainerManagement (TPCM). In case no manifest is found in the image labels, the framework treats it as a TPCM package and creates a default manifest for it. During installation, a new manifest file is created with a specified name using the --name option. Users can use the "sonic-package-manager manifests create/update/delete" commands to modify or delete the manifest file. The location for custom local package manifest files is set to "/var/lib/sonic-package-manager/manifests/". Finally, the "sonic-package-manager update" command can be used to apply the updated manifest file to the running TPCM docker. #### How to verify it sonic-package-manager install --from-repository <package without manifest, say httpd> --name mytpcm sonic-package manager install --from-tarball <local tar/scp tar/sftp tar/http tar> --name <> --use-local-manifest Manifests Commands(tpcm): sonic-package-manager manifests create <> --from-json <> sonic-package-manager manifests update <> --from-json <> sonic-package-manager manifests list sonic-package-manager manifests show <> sonic-package-manager manifests delete <> sonic-package manager update <package>
1 parent 61d0ec9 commit 550816b

File tree

13 files changed

+1135
-70
lines changed

13 files changed

+1135
-70
lines changed

setup.py

+1
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@
257257
'xmltodict==0.12.0',
258258
'lazy-object-proxy',
259259
'six==1.16.0',
260+
'scp==0.14.5',
260261
] + sonic_dependencies,
261262
setup_requires= [
262263
'pytest-runner',

sonic_installer/main.py

+4
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,8 @@ def migrate_sonic_packages(bootloader, binary_image_version):
337337
new_image_docker_mount = os.path.join(new_image_mount, "var", "lib", "docker")
338338
docker_default_config = os.path.join(new_image_mount, "etc", "default", "docker")
339339
docker_default_config_backup = os.path.join(new_image_mount, TMP_DIR, "docker_config_backup")
340+
custom_manifests_path = os.path.join(PACKAGE_MANAGER_DIR, "manifests")
341+
new_image_package_directory_path = os.path.join(new_image_mount, "var", "lib", "sonic-package-manager")
340342

341343
if not os.path.isdir(new_image_docker_dir):
342344
# NOTE: This codepath can be reached if the installation process did not
@@ -372,6 +374,8 @@ def migrate_sonic_packages(bootloader, binary_image_version):
372374
run_command_or_raise(["chroot", new_image_mount, DOCKER_CTL_SCRIPT, "start"])
373375
docker_started = True
374376
run_command_or_raise(["cp", packages_path, os.path.join(new_image_mount, TMP_DIR, packages_file)])
377+
run_command_or_raise(["mkdir", "-p", custom_manifests_path])
378+
run_command_or_raise(["cp", "-arf", custom_manifests_path, new_image_package_directory_path])
375379
run_command_or_raise(["touch", os.path.join(new_image_mount, "tmp", DOCKERD_SOCK)])
376380
run_command_or_raise(["mount", "--bind",
377381
os.path.join(VAR_RUN_PATH, DOCKERD_SOCK),

sonic_package_manager/main.py

+128-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from sonic_package_manager.errors import PackageManagerError
1616
from sonic_package_manager.logger import log
1717
from sonic_package_manager.manager import PackageManager
18+
from sonic_package_manager.manifest import MANIFESTS_LOCATION
1819

1920
BULLET_UC = '\u2022'
2021

@@ -157,6 +158,13 @@ def repository(ctx):
157158
pass
158159

159160

161+
@cli.group()
162+
@click.pass_context
163+
def manifests(ctx):
164+
""" Custom local Manifest management commands. """
165+
166+
pass
167+
160168
@cli.group()
161169
@click.pass_context
162170
def show(ctx):
@@ -280,6 +288,73 @@ def changelog(ctx,
280288
exit_cli(f'Failed to print package changelog: {err}', fg='red')
281289

282290

291+
@manifests.command('create')
292+
@click.pass_context
293+
@click.argument('name', type=click.Path())
294+
@click.option('--from-json', type=str, help='specify manifest json file')
295+
@root_privileges_required
296+
def create_manifest(ctx, name, from_json):
297+
"""Create a new custom local manifest file."""
298+
299+
manager: PackageManager = ctx.obj
300+
try:
301+
manager.create_package_manifest(name, from_json)
302+
except Exception as e:
303+
click.echo("Error: Manifest {} creation failed - {}".format(name, str(e)))
304+
return
305+
306+
307+
@manifests.command('update')
308+
@click.pass_context
309+
@click.argument('name', type=click.Path())
310+
@click.option('--from-json', type=str, required=True)
311+
@root_privileges_required
312+
def update_manifest(ctx, name, from_json):
313+
"""Update an existing custom local manifest file with new one."""
314+
315+
manager: PackageManager = ctx.obj
316+
try:
317+
manager.update_package_manifest(name, from_json)
318+
except Exception as e:
319+
click.echo(f"Error occurred while updating manifest '{name}': {e}")
320+
return
321+
322+
323+
@manifests.command('delete')
324+
@click.pass_context
325+
@click.argument('name', type=click.Path())
326+
@root_privileges_required
327+
def delete_manifest(ctx, name):
328+
"""Delete a custom local manifest file."""
329+
manager: PackageManager = ctx.obj
330+
try:
331+
manager.delete_package_manifest(name)
332+
except Exception as e:
333+
click.echo("Error: Failed to delete manifest file '{}'. {}".format(name, e))
334+
335+
336+
@manifests.command('show')
337+
@click.pass_context
338+
@click.argument('name', type=click.Path())
339+
@root_privileges_required
340+
def show_manifest(ctx, name):
341+
"""Show the contents of custom local manifest file."""
342+
manager: PackageManager = ctx.obj
343+
try:
344+
manager.show_package_manifest(name)
345+
except FileNotFoundError:
346+
click.echo("Manifest file '{}' not found.".format(name))
347+
348+
349+
@manifests.command('list')
350+
@click.pass_context
351+
@root_privileges_required
352+
def list_manifests(ctx):
353+
"""List all custom local manifest files."""
354+
manager: PackageManager = ctx.obj
355+
manager.list_package_manifest()
356+
357+
283358
@repository.command()
284359
@click.argument('name', type=str)
285360
@click.argument('repository', type=str)
@@ -334,6 +409,14 @@ def remove(ctx, name):
334409
help='Allow package downgrade. By default an attempt to downgrade the package '
335410
'will result in a failure since downgrade might not be supported by the package, '
336411
'thus requires explicit request from the user.')
412+
@click.option('--use-local-manifest',
413+
is_flag=True,
414+
default=None,
415+
help='Use locally created custom manifest file. ',
416+
hidden=True)
417+
@click.option('--name',
418+
type=str,
419+
help='custom name for the package')
337420
@add_options(PACKAGE_SOURCE_OPTIONS)
338421
@add_options(PACKAGE_COMMON_OPERATION_OPTIONS)
339422
@add_options(PACKAGE_COMMON_INSTALL_OPTIONS)
@@ -348,7 +431,9 @@ def install(ctx,
348431
enable,
349432
set_owner,
350433
skip_host_plugins,
351-
allow_downgrade):
434+
allow_downgrade,
435+
use_local_manifest,
436+
name):
352437
""" Install/Upgrade package using [PACKAGE_EXPR] in format "<name>[=<version>|@<reference>]".
353438
354439
The repository to pull the package from is resolved by lookup in package database,
@@ -378,16 +463,58 @@ def install(ctx,
378463
if allow_downgrade is not None:
379464
install_opts['allow_downgrade'] = allow_downgrade
380465

466+
if use_local_manifest:
467+
if not name:
468+
click.echo('name argument is not provided to use local manifest')
469+
return
470+
original_file = os.path.join(MANIFESTS_LOCATION, name)
471+
if not os.path.exists(original_file):
472+
click.echo(f'Local Manifest file for {name} does not exists to install')
473+
return
474+
381475
try:
382476
manager.install(package_expr,
383477
from_repository,
384478
from_tarball,
479+
use_local_manifest,
480+
name,
385481
**install_opts)
386482
except Exception as err:
387483
exit_cli(f'Failed to install {package_source}: {err}', fg='red')
388484
except KeyboardInterrupt:
389485
exit_cli('Operation canceled by user', fg='red')
390486

487+
# At the end of sonic-package-manager install, a new manifest file is created with the name.
488+
# At the end of sonic-package-manager uninstall name,
489+
# this manifest file name and name.edit will be deleted.
490+
# At the end of sonic-package-manager update,
491+
# we need to mv maniests name.edit to name in case of success, else keep it as such.
492+
# So during sonic-package-manager update,
493+
# we could take old package from name and new package from edit and at the end, follow 3rd point
494+
495+
496+
@cli.command()
497+
@add_options(PACKAGE_COMMON_OPERATION_OPTIONS)
498+
@add_options(PACKAGE_COMMON_INSTALL_OPTIONS)
499+
@click.argument('name')
500+
@click.pass_context
501+
@root_privileges_required
502+
def update(ctx, name, force, yes, skip_host_plugins):
503+
""" Update package to the updated manifest file. """
504+
505+
manager: PackageManager = ctx.obj
506+
507+
update_opts = {
508+
'force': force,
509+
'skip_host_plugins': skip_host_plugins,
510+
'update_only': True,
511+
}
512+
try:
513+
manager.update(name, **update_opts)
514+
except Exception as err:
515+
exit_cli(f'Failed to update package {name}: {err}', fg='red')
516+
except KeyboardInterrupt:
517+
exit_cli('Operation canceled by user', fg='red')
391518

392519
@cli.command()
393520
@add_options(PACKAGE_COMMON_OPERATION_OPTIONS)

0 commit comments

Comments
 (0)