diff options
author | Radim JanalĂk <radim.janalik@gmail.com> | 2024-04-12 03:43:13 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-11 19:43:13 -0600 |
commit | d23e06c27e91b55e139f38af17f91a39925e55b2 (patch) | |
tree | 424ac4f5496380d0d5b1ed6d13c48defbee80b81 | |
parent | b76e9a887b72a71bb38a86f85b84375bea2e100e (diff) | |
download | spack-d23e06c27e91b55e139f38af17f91a39925e55b2.tar.gz spack-d23e06c27e91b55e139f38af17f91a39925e55b2.tar.bz2 spack-d23e06c27e91b55e139f38af17f91a39925e55b2.tar.xz spack-d23e06c27e91b55e139f38af17f91a39925e55b2.zip |
Allow packages to be pushed to build cache after install from source (#42423)
This commit adds a property `autopush` to mirrors. When true, every source build is immediately followed by a push to the build cache. This is useful in ephemeral environments such as CI / containers.
To enable autopush on existing build caches, use `spack mirror set --autopush <name>`. The same flag can be used in `spack mirror add`.
-rw-r--r-- | lib/spack/docs/binary_caches.rst | 34 | ||||
-rw-r--r-- | lib/spack/spack/cmd/mirror.py | 25 | ||||
-rw-r--r-- | lib/spack/spack/hooks/autopush.py | 27 | ||||
-rw-r--r-- | lib/spack/spack/mirror.py | 45 | ||||
-rw-r--r-- | lib/spack/spack/schema/mirrors.py | 1 | ||||
-rw-r--r-- | lib/spack/spack/test/cmd/buildcache.py | 19 | ||||
-rw-r--r-- | lib/spack/spack/test/cmd/mirror.py | 24 | ||||
-rwxr-xr-x | share/spack/spack-completion.bash | 4 | ||||
-rwxr-xr-x | share/spack/spack-completion.fish | 10 |
9 files changed, 169 insertions, 20 deletions
diff --git a/lib/spack/docs/binary_caches.rst b/lib/spack/docs/binary_caches.rst index 27d72877a7..6d98bc8186 100644 --- a/lib/spack/docs/binary_caches.rst +++ b/lib/spack/docs/binary_caches.rst @@ -220,6 +220,40 @@ section of the configuration: .. _binary_caches_oci: +--------------------------------- +Automatic push to a build cache +--------------------------------- + +Sometimes it is convenient to push packages to a build cache as soon as they are installed. Spack can do this by setting autopush flag when adding a mirror: + +.. code-block:: console + + $ spack mirror add --autopush <name> <url or path> + +Or the autopush flag can be set for an existing mirror: + +.. code-block:: console + + $ spack mirror set --autopush <name> # enable automatic push for an existing mirror + $ spack mirror set --no-autopush <name> # disable automatic push for an existing mirror + +Then after installing a package it is automatically pushed to all mirrors with ``autopush: true``. The command + +.. code-block:: console + + $ spack install <package> + +will have the same effect as + +.. code-block:: console + + $ spack install <package> + $ spack buildcache push <cache> <package> # for all caches with autopush: true + +.. note:: + + Packages are automatically pushed to a build cache only if they are built from source. + ----------------------------------------- OCI / Docker V2 registries as build cache ----------------------------------------- diff --git a/lib/spack/spack/cmd/mirror.py b/lib/spack/spack/cmd/mirror.py index 0faf3123c7..4b7fa90e55 100644 --- a/lib/spack/spack/cmd/mirror.py +++ b/lib/spack/spack/cmd/mirror.py @@ -108,6 +108,11 @@ def setup_parser(subparser): "and source use `--type binary --type source` (default)" ), ) + add_parser.add_argument( + "--autopush", + action="store_true", + help=("set mirror to push automatically after installation"), + ) add_parser_signed = add_parser.add_mutually_exclusive_group(required=False) add_parser_signed.add_argument( "--unsigned", @@ -175,6 +180,21 @@ def setup_parser(subparser): ), ) set_parser.add_argument("--url", help="url of mirror directory from 'spack mirror create'") + set_parser_autopush = set_parser.add_mutually_exclusive_group(required=False) + set_parser_autopush.add_argument( + "--autopush", + help="set mirror to push automatically after installation", + action="store_true", + default=None, + dest="autopush", + ) + set_parser_autopush.add_argument( + "--no-autopush", + help="set mirror to not push automatically after installation", + action="store_false", + default=None, + dest="autopush", + ) set_parser_unsigned = set_parser.add_mutually_exclusive_group(required=False) set_parser_unsigned.add_argument( "--unsigned", @@ -218,6 +238,7 @@ def mirror_add(args): or args.type or args.oci_username or args.oci_password + or args.autopush or args.signed is not None ): connection = {"url": args.url} @@ -234,6 +255,8 @@ def mirror_add(args): if args.type: connection["binary"] = "binary" in args.type connection["source"] = "source" in args.type + if args.autopush: + connection["autopush"] = args.autopush if args.signed is not None: connection["signed"] = args.signed mirror = spack.mirror.Mirror(connection, name=args.name) @@ -270,6 +293,8 @@ def _configure_mirror(args): changes["access_pair"] = [args.oci_username, args.oci_password] if getattr(args, "signed", None) is not None: changes["signed"] = args.signed + if getattr(args, "autopush", None) is not None: + changes["autopush"] = args.autopush # argparse cannot distinguish between --binary and --no-binary when same dest :( # notice that set-url does not have these args, so getattr diff --git a/lib/spack/spack/hooks/autopush.py b/lib/spack/spack/hooks/autopush.py new file mode 100644 index 0000000000..b49f741ad9 --- /dev/null +++ b/lib/spack/spack/hooks/autopush.py @@ -0,0 +1,27 @@ +# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) + +import llnl.util.tty as tty + +import spack.binary_distribution as bindist +import spack.mirror + + +def post_install(spec, explicit): + # Push package to all buildcaches with autopush==True + + # Do nothing if package was not installed from source + pkg = spec.package + if pkg.installed_from_binary_cache: + return + + # Push the package to all autopush mirrors + for mirror in spack.mirror.MirrorCollection(binary=True, autopush=True).values(): + bindist.push_or_raise( + spec, + mirror.push_url, + bindist.PushOptions(force=True, regenerate_index=False, unsigned=not mirror.signed), + ) + tty.msg(f"{spec.name}: Pushed to build cache: '{mirror.name}'") diff --git a/lib/spack/spack/mirror.py b/lib/spack/spack/mirror.py index 2f1b9966ce..45681be853 100644 --- a/lib/spack/spack/mirror.py +++ b/lib/spack/spack/mirror.py @@ -138,6 +138,12 @@ class Mirror: return isinstance(self._data, str) or self._data.get("signed", True) @property + def autopush(self) -> bool: + if isinstance(self._data, str): + return False + return self._data.get("autopush", False) + + @property def fetch_url(self): """Get the valid, canonicalized fetch URL""" return self.get_url("fetch") @@ -150,7 +156,7 @@ class Mirror: def _update_connection_dict(self, current_data: dict, new_data: dict, top_level: bool): keys = ["url", "access_pair", "access_token", "profile", "endpoint_url"] if top_level: - keys += ["binary", "source", "signed"] + keys += ["binary", "source", "signed", "autopush"] changed = False for key in keys: if key in new_data and current_data.get(key) != new_data[key]: @@ -286,6 +292,7 @@ class MirrorCollection(collections.abc.Mapping): scope=None, binary: Optional[bool] = None, source: Optional[bool] = None, + autopush: Optional[bool] = None, ): """Initialize a mirror collection. @@ -297,21 +304,27 @@ class MirrorCollection(collections.abc.Mapping): If None, do not filter on binary mirrors. source: If True, only include source mirrors. If False, omit source mirrors. - If None, do not filter on source mirrors.""" - self._mirrors = { - name: Mirror(data=mirror, name=name) - for name, mirror in ( - mirrors.items() - if mirrors is not None - else spack.config.get("mirrors", scope=scope).items() - ) - } - - if source is not None: - self._mirrors = {k: v for k, v in self._mirrors.items() if v.source == source} - - if binary is not None: - self._mirrors = {k: v for k, v in self._mirrors.items() if v.binary == binary} + If None, do not filter on source mirrors. + autopush: If True, only include mirrors that have autopush enabled. + If False, omit mirrors that have autopush enabled. + If None, do not filter on autopush.""" + mirrors_data = ( + mirrors.items() + if mirrors is not None + else spack.config.get("mirrors", scope=scope).items() + ) + mirrors = (Mirror(data=mirror, name=name) for name, mirror in mirrors_data) + + def _filter(m: Mirror): + if source is not None and m.source != source: + return False + if binary is not None and m.binary != binary: + return False + if autopush is not None and m.autopush != autopush: + return False + return True + + self._mirrors = {m.name: m for m in mirrors if _filter(m)} def __eq__(self, other): return self._mirrors == other._mirrors diff --git a/lib/spack/spack/schema/mirrors.py b/lib/spack/spack/schema/mirrors.py index c3959374a7..9a56f1a7e2 100644 --- a/lib/spack/spack/schema/mirrors.py +++ b/lib/spack/spack/schema/mirrors.py @@ -46,6 +46,7 @@ mirror_entry = { "signed": {"type": "boolean"}, "fetch": fetch_and_push, "push": fetch_and_push, + "autopush": {"type": "boolean"}, **connection, # type: ignore }, } diff --git a/lib/spack/spack/test/cmd/buildcache.py b/lib/spack/spack/test/cmd/buildcache.py index 12ef53e856..40d2cbccba 100644 --- a/lib/spack/spack/test/cmd/buildcache.py +++ b/lib/spack/spack/test/cmd/buildcache.py @@ -169,6 +169,25 @@ def test_update_key_index( assert "index.json" in key_dir_list +def test_buildcache_autopush(tmp_path, install_mockery, mock_fetch): + """Test buildcache with autopush""" + mirror_dir = tmp_path / "mirror" + mirror_autopush_dir = tmp_path / "mirror_autopush" + + mirror("add", "--unsigned", "mirror", mirror_dir.as_uri()) + mirror("add", "--autopush", "--unsigned", "mirror-autopush", mirror_autopush_dir.as_uri()) + + s = Spec("libdwarf").concretized() + + # Install and generate build cache index + s.package.do_install() + + metadata_file = spack.binary_distribution.tarball_name(s, ".spec.json") + + assert not (mirror_dir / "build_cache" / metadata_file).exists() + assert (mirror_autopush_dir / "build_cache" / metadata_file).exists() + + def test_buildcache_sync( mutable_mock_env_path, install_mockery_mutable_config, diff --git a/lib/spack/spack/test/cmd/mirror.py b/lib/spack/spack/test/cmd/mirror.py index ca4c0dd8ce..fe0a996e14 100644 --- a/lib/spack/spack/test/cmd/mirror.py +++ b/lib/spack/spack/test/cmd/mirror.py @@ -407,3 +407,27 @@ def test_mirror_add_set_signed(mutable_config): assert spack.config.get("mirrors:example") == {"url": "http://example.com", "signed": False} mirror("set", "--signed", "example") assert spack.config.get("mirrors:example") == {"url": "http://example.com", "signed": True} + + +def test_mirror_add_set_autopush(mutable_config): + # Add mirror without autopush + mirror("add", "example", "http://example.com") + assert spack.config.get("mirrors:example") == "http://example.com" + mirror("set", "--no-autopush", "example") + assert spack.config.get("mirrors:example") == {"url": "http://example.com", "autopush": False} + mirror("set", "--autopush", "example") + assert spack.config.get("mirrors:example") == {"url": "http://example.com", "autopush": True} + mirror("set", "--no-autopush", "example") + assert spack.config.get("mirrors:example") == {"url": "http://example.com", "autopush": False} + mirror("remove", "example") + + # Add mirror with autopush + mirror("add", "--autopush", "example", "http://example.com") + assert spack.config.get("mirrors:example") == {"url": "http://example.com", "autopush": True} + mirror("set", "--autopush", "example") + assert spack.config.get("mirrors:example") == {"url": "http://example.com", "autopush": True} + mirror("set", "--no-autopush", "example") + assert spack.config.get("mirrors:example") == {"url": "http://example.com", "autopush": False} + mirror("set", "--autopush", "example") + assert spack.config.get("mirrors:example") == {"url": "http://example.com", "autopush": True} + mirror("remove", "example") diff --git a/share/spack/spack-completion.bash b/share/spack/spack-completion.bash index d0abb53d78..2b2dec2367 100755 --- a/share/spack/spack-completion.bash +++ b/share/spack/spack-completion.bash @@ -1441,7 +1441,7 @@ _spack_mirror_destroy() { _spack_mirror_add() { if $list_options then - SPACK_COMPREPLY="-h --help --scope --type --unsigned --signed --s3-access-key-id --s3-access-key-secret --s3-access-token --s3-profile --s3-endpoint-url --oci-username --oci-password" + SPACK_COMPREPLY="-h --help --scope --type --autopush --unsigned --signed --s3-access-key-id --s3-access-key-secret --s3-access-token --s3-profile --s3-endpoint-url --oci-username --oci-password" else _mirrors fi @@ -1477,7 +1477,7 @@ _spack_mirror_set_url() { _spack_mirror_set() { if $list_options then - SPACK_COMPREPLY="-h --help --push --fetch --type --url --unsigned --signed --scope --s3-access-key-id --s3-access-key-secret --s3-access-token --s3-profile --s3-endpoint-url --oci-username --oci-password" + SPACK_COMPREPLY="-h --help --push --fetch --type --url --autopush --no-autopush --unsigned --signed --scope --s3-access-key-id --s3-access-key-secret --s3-access-token --s3-profile --s3-endpoint-url --oci-username --oci-password" else _mirrors fi diff --git a/share/spack/spack-completion.fish b/share/spack/spack-completion.fish index d8399955be..24cb8e5040 100755 --- a/share/spack/spack-completion.fish +++ b/share/spack/spack-completion.fish @@ -2253,7 +2253,7 @@ complete -c spack -n '__fish_spack_using_command mirror destroy' -l mirror-url - complete -c spack -n '__fish_spack_using_command mirror destroy' -l mirror-url -r -d 'find mirror to destroy by url' # spack mirror add -set -g __fish_spack_optspecs_spack_mirror_add h/help scope= type= unsigned signed s3-access-key-id= s3-access-key-secret= s3-access-token= s3-profile= s3-endpoint-url= oci-username= oci-password= +set -g __fish_spack_optspecs_spack_mirror_add h/help scope= type= autopush unsigned signed s3-access-key-id= s3-access-key-secret= s3-access-token= s3-profile= s3-endpoint-url= oci-username= oci-password= complete -c spack -n '__fish_spack_using_command_pos 0 mirror add' -f complete -c spack -n '__fish_spack_using_command mirror add' -s h -l help -f -a help complete -c spack -n '__fish_spack_using_command mirror add' -s h -l help -d 'show this help message and exit' @@ -2261,6 +2261,8 @@ complete -c spack -n '__fish_spack_using_command mirror add' -l scope -r -f -a ' complete -c spack -n '__fish_spack_using_command mirror add' -l scope -r -d 'configuration scope to modify' complete -c spack -n '__fish_spack_using_command mirror add' -l type -r -f -a 'binary source' complete -c spack -n '__fish_spack_using_command mirror add' -l type -r -d 'specify the mirror type: for both binary and source use `--type binary --type source` (default)' +complete -c spack -n '__fish_spack_using_command mirror add' -l autopush -f -a autopush +complete -c spack -n '__fish_spack_using_command mirror add' -l autopush -d 'set mirror to push automatically after installation' complete -c spack -n '__fish_spack_using_command mirror add' -l unsigned -f -a signed complete -c spack -n '__fish_spack_using_command mirror add' -l unsigned -d 'do not require signing and signature verification when pushing and installing from this build cache' complete -c spack -n '__fish_spack_using_command mirror add' -l signed -f -a signed @@ -2323,7 +2325,7 @@ complete -c spack -n '__fish_spack_using_command mirror set-url' -l oci-password complete -c spack -n '__fish_spack_using_command mirror set-url' -l oci-password -r -d 'password to use to connect to this OCI mirror' # spack mirror set -set -g __fish_spack_optspecs_spack_mirror_set h/help push fetch type= url= unsigned signed scope= s3-access-key-id= s3-access-key-secret= s3-access-token= s3-profile= s3-endpoint-url= oci-username= oci-password= +set -g __fish_spack_optspecs_spack_mirror_set h/help push fetch type= url= autopush no-autopush unsigned signed scope= s3-access-key-id= s3-access-key-secret= s3-access-token= s3-profile= s3-endpoint-url= oci-username= oci-password= complete -c spack -n '__fish_spack_using_command_pos 0 mirror set' -f -a '(__fish_spack_mirrors)' complete -c spack -n '__fish_spack_using_command mirror set' -s h -l help -f -a help complete -c spack -n '__fish_spack_using_command mirror set' -s h -l help -d 'show this help message and exit' @@ -2335,6 +2337,10 @@ complete -c spack -n '__fish_spack_using_command mirror set' -l type -r -f -a 'b complete -c spack -n '__fish_spack_using_command mirror set' -l type -r -d 'specify the mirror type: for both binary and source use `--type binary --type source`' complete -c spack -n '__fish_spack_using_command mirror set' -l url -r -f -a url complete -c spack -n '__fish_spack_using_command mirror set' -l url -r -d 'url of mirror directory from \'spack mirror create\'' +complete -c spack -n '__fish_spack_using_command mirror set' -l autopush -f -a autopush +complete -c spack -n '__fish_spack_using_command mirror set' -l autopush -d 'set mirror to push automatically after installation' +complete -c spack -n '__fish_spack_using_command mirror set' -l no-autopush -f -a autopush +complete -c spack -n '__fish_spack_using_command mirror set' -l no-autopush -d 'set mirror to not push automatically after installation' complete -c spack -n '__fish_spack_using_command mirror set' -l unsigned -f -a signed complete -c spack -n '__fish_spack_using_command mirror set' -l unsigned -d 'do not require signing and signature verification when pushing and installing from this build cache' complete -c spack -n '__fish_spack_using_command mirror set' -l signed -f -a signed |