Skip to content

Commit 7387d95

Browse files
authored
Several updates to config and environment settings (#403)
* several updates to config and environment settings * temporary fix of moving registry entiry to end (so no comments are eaten) and printing warning * ensuring that multiple podman images are deleted if no tag is provided * fix for ruamel deleting comments - we need to preserve its type for the list * fixing typo for shpc uninstall * try doing uninstall after test, and looking at docker/podman images before and after Signed-off-by: vsoch <[email protected]>
1 parent 095ab18 commit 7387d95

20 files changed

+326
-68
lines changed

.github/workflows/test.yml

+1
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ jobs:
101101
grep --quiet 'Python 3.9.5' test_output
102102
rm test_output
103103
shpc uninstall --force python:3.9.5-alpine
104+
104105
105106
- name: Run python module tests (tcsh)
106107
shell: tcsh -e {0}

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ and **Merged pull requests**. Critical items to know are:
1414
The versions coincide with releases on pip. Only major versions will be released as tags on Github.
1515

1616
## [0.0.x](https://github.scom/singularityhub/singularity-hpc/tree/master) (0.0.x)
17+
- Allow environment variables in settings (0.0.29)
18+
- User settings file creation and use with shpc config inituser
19+
- registry is now a list to support multiple registry locations
20+
- config supports add/remove to append/delete from list
1721
- Add test for docker and podman (0.0.28)
1822
- namespace as format string for command named renamed to repository
1923
- shpc test/uninstall should be run for all tests

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ And container technologies:
1414

1515
- [Singularity](https://github.com/sylabs/singularity)
1616
- [Podman](https://podman.io)
17+
- [Docker](https://docker.io)
1718

1819
Coming soon:
1920

docs/getting_started/user-guide.rst

+38-13
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,17 @@ you should do that first.
1313
Why shpc?
1414
=========
1515

16-
While the library is currently focused on Singularity containers (hence
17-
the name) it's created to be modular, meaning that if another container technology
18-
is wanted, it can be added. The module name would still be appropriate, as
19-
singularity does imply a single entity that is "one library to rule them all!"
16+
Singularity Registry HPC is created to be modular, meaning that we support a distinct
17+
set of container technologies and module systems. The name of the library "Singularity
18+
Registry HPC" does not refer specifically to the container technology "Singularity,"
19+
but more generally implies the same spirit -- a single entity that is "one library to rule them all!"
20+
2021

2122
What is a registry?
2223
===================
2324

2425
A registry consists of a database of local containers configuration files, ``container.yaml``
25-
files organized in the root of the shpc install in the ``registry`` folder. The namespace
26+
files organized in the root of the shpc install in one of the ``registry`` folders. The namespace
2627
is organized by Docker unique resources identifiers. When you install an identifier
2728
as we saw above, the container binaries and customized module files are added to
2829
the ``module_dir`` defined in your settings, which defaults to ``modules`` in the
@@ -99,15 +100,27 @@ Setup
99100
=====
100101

101102
Setup includes, after installation, editing any configuration values to
102-
customize your install. The defaults are likely suitable for most.
103-
For any configuration value that you might set, the following variables
104-
are available to you:
103+
customize your install. The configuration file will default to ``shpc/settings.yml``
104+
in the installed module, however you can create your own user settings file to
105+
take preference over this one as follows:
106+
107+
.. code-block:: console
108+
109+
$ shpc config userinit
110+
111+
112+
The defaults in either file are likely suitable for most. For any configuration value
113+
that you might set, the following variables are available to you:
105114

106115
- ``$install_dir``: the shpc folder
107116
- ``$root_dir``: the parent directory of shpc (where this README.md is located)
108117

109118

110-
A summary table of variables is included below, and then further discussed in detail.
119+
Additionally, the variables ``module_base``, ``container_base``, and ``registry``
120+
can be set with environment variables that will be expanded at runtime. You cannot
121+
use the protected set of substitution variables (``$install_dir`` and ``$install_root``)
122+
as environment variables, as they will be subbed in by shpc before environment
123+
variable replacement. A summary table of variables is included below, and then further discussed in detail.
111124

112125

113126
.. list-table:: Title
@@ -121,8 +134,8 @@ A summary table of variables is included below, and then further discussed in de
121134
- Set a default module system. Currently lmod and tcl are supported
122135
- [lmod, tcl]
123136
* - registry
124-
- The full path to the registry folder (with subfolders with container.yaml recipes)
125-
- $root_dir/registry
137+
- A list of full paths to one or more registry folders (with subfolders with container.yaml recipes)
138+
- [$root_dir/registry]
126139
* - module_base
127140
- The install directory for modules. Defaults to the install directory/modules
128141
- $root_dir/modules
@@ -271,8 +284,10 @@ directory.
271284
Registry
272285
--------
273286

274-
The registry folder in the root of the repository, but you can change it to
275-
be a custom one with the config variable ``registry``
287+
The registry parameter is a list of one or more registry locations (filesystem
288+
directories) where shpc will search for ``container.yaml`` files. The default
289+
registry shipped with shpc is the folder in the root of the repository, but
290+
you can add or remove entries via the config variable ``registry``
276291

277292

278293
.. code-block:: console
@@ -409,6 +424,8 @@ file directly, or you can use ``shpc config``, which will accept:
409424

410425
- set to set a parameter and value
411426
- get to get a parameter by name
427+
- add to add a value to a parameter that is a list (e.g., registry)
428+
- remove to remove a value from a parameter that is a list
412429

413430
The following example shows changing the default module_base path from the install directory modules folder.
414431

@@ -428,6 +445,14 @@ And then to get values:
428445
$ shpc config get module_base
429446
430447
448+
And to add and remove a value to a list:
449+
450+
.. code-block::console
451+
452+
$ shpc config add registry:/tmp/registry
453+
$ shpc config remove registry:/tmp/registry
454+
455+
431456
You can also open the config in the editor defined in settings at ``config_editor``
432457

433458
.. code-block:: console

docs/index.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ And container technologies:
2020

2121
- `Singularity <https://github.com/sylabs/singularity>`_
2222
- `Podman <https://podman.io>`_
23+
- `Docker <https://www.docker.com/>`_
2324

2425
And coming soon:
2526

2627
- `Shifter <https://github.com/NERSC/shifter>`_
27-
- `Docker <https://www.docker.com/>`_
2828
- `Sarus <https://github.com/eth-cscs/sarus>`_
2929

3030

shpc/client/__init__.py

+18-2
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def get_parser():
4343
parser.add_argument(
4444
"--version",
4545
dest="version",
46-
help="suppress additional output.",
46+
help="show software version.",
4747
default=False,
4848
action="store_true",
4949
)
@@ -142,10 +142,26 @@ def get_parser():
142142
help="update configuration settings. Use set or get to see or set information.",
143143
formatter_class=argparse.RawTextHelpFormatter,
144144
)
145+
146+
config.add_argument(
147+
"--central",
148+
"-c",
149+
dest="central",
150+
help="make edits to the central config file.",
151+
default=False,
152+
action="store_true",
153+
)
154+
145155
config.add_argument(
146156
"params",
147157
nargs="*",
148-
help="Set or get a config value, or edit the config.\nshpc config set key:value\nshpc config get key\nshpc edit",
158+
help="""Set or get a config value, edit the config, add or remove a list variable, or create a user-specific config.
159+
shpc config set key:value
160+
shpc config get key
161+
shpc edit
162+
shpc config inituser
163+
shpc config add registry:/tmp/registry
164+
shpc config remove registry:/tmp/registry""",
149165
type=str,
150166
)
151167
# Generate markdown docs for a container registry entry

shpc/client/config.py

+17-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
__copyright__ = "Copyright 2021, Vanessa Sochat"
33
__license__ = "MPL 2.0"
44

5+
import shpc.defaults as defaults
56
from shpc.logger import logger
67
import sys
78

@@ -18,15 +19,21 @@ def main(args, parser, extra, subparser):
1819
# The first "param" is either set of get
1920
command = args.params.pop(0)
2021

22+
# If the user wants the central config file
23+
if args.central:
24+
args.settings_file = defaults.default_settings_file
25+
2126
validate = True if not command == "edit" else False
2227
cli = get_client(
2328
quiet=args.quiet, settings_file=args.settings_file, validate=validate
2429
)
2530

2631
# For each new setting, update and save!
32+
if command == "inituser":
33+
return cli.settings.inituser()
2734
if command == "edit":
2835
return cli.settings.edit()
29-
elif command == "set":
36+
elif command in ["set", "add", "remove"]:
3037
for param in args.params:
3138
if ":" not in param:
3239
logger.warning(
@@ -35,8 +42,15 @@ def main(args, parser, extra, subparser):
3542
)
3643
continue
3744
key, value = param.split(":", 1)
38-
cli.settings.set(key, value)
39-
logger.info("Updated %s to be %s" % (key, value))
45+
if command == "set":
46+
cli.settings.set(key, value)
47+
logger.info("Updated %s to be %s" % (key, value))
48+
elif command == "add":
49+
cli.settings.add(key, value)
50+
logger.info("Added %s to %s" % (key, value))
51+
elif command == "remove":
52+
cli.settings.remove(key, value)
53+
logger.info("Removed %s from %s" % (key, value))
4054

4155
# Save settings
4256
cli.settings.save()

shpc/defaults.py

+8
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@
1212
# The default settings file in the install root
1313
default_settings_file = os.path.join(reps["$install_dir"], "settings.yml")
1414

15+
# The user settings file can be created to over-ride default
16+
user_settings_file = os.path.join(
17+
os.path.expanduser("~/.singularity-hpc"), "settings.yml"
18+
)
19+
20+
# variables in settings that allow environment variable expansion
21+
allowed_envars = ["container_base", "module_base", "registry"]
22+
1523
# The GitHub repository with recipes
1624
github_url = "https://github.com/singularityhub/singularity-hpc"
1725

shpc/main/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import shpc.utils
99
from shpc.logger import logger
10+
import shpc.defaults
1011

1112

1213
def get_client(quiet=False, **kwargs):

shpc/main/client.py

+16-9
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,18 @@ def add_namespace(self, name):
8686
name = "%s/%s" % (self.settings.namespace.strip("/"), name)
8787
return name
8888

89+
def load_registry_config(self, name):
90+
"""
91+
Given an identifier, find the first match in the registry.
92+
"""
93+
for registry, fullpath in self.container.iter_registry():
94+
package_dir = os.path.join(registry, name)
95+
package_file = os.path.join(package_dir, "container.yaml")
96+
if package_file == fullpath:
97+
return container.ContainerConfig(package_file)
98+
99+
logger.exit("%s is not a known recipe in any registry." % name)
100+
89101
def _load_container(self, name, tag=None):
90102
"""
91103
Given a name and an optional tag to default to, load a package
@@ -94,12 +106,8 @@ def _load_container(self, name, tag=None):
94106
if ":" in name:
95107
name, tag = name.split(":", 1)
96108

97-
# The recipe folder must exist in the registry
98-
package_dir = os.path.join(self.settings.registry, name)
99-
package_file = os.path.join(package_dir, "container.yaml")
100-
config = container.ContainerConfig(package_file)
101-
102109
# If the user provides a tag, set it
110+
config = self.load_registry_config(name)
103111
config.set_tag(tag)
104112
return config
105113

@@ -169,6 +177,7 @@ def cleanup(tmpdir):
169177
if stage:
170178
logger.info(tmpdir)
171179
else:
180+
self.uninstall(module_name, force=True)
172181
cleanup(tmpdir)
173182

174183
def check(self, module_name):
@@ -205,12 +214,10 @@ def show(self, name, names_only=False, out=None, filter_string=None):
205214
out = out or sys.stdout
206215

207216
# List the known registry modules
208-
for fullpath in utils.recursive_find(self.settings.registry):
217+
for registry, fullpath in self.container.iter_registry():
209218
if fullpath.endswith("container.yaml"):
210219
module_name = (
211-
os.path.dirname(fullpath)
212-
.replace(self.settings.registry, "")
213-
.strip(os.sep)
220+
os.path.dirname(fullpath).replace(registry, "").strip(os.sep)
214221
)
215222

216223
# If the user has provided a filter, honor it

shpc/main/container/base.py

+21-9
Original file line numberDiff line numberDiff line change
@@ -90,24 +90,36 @@ def container_dir(self, name):
9090
return os.path.join(self.settings.module_base, name)
9191
return os.path.join(self.settings.container_base, name)
9292

93+
def iter_registry(self):
94+
"""
95+
Iterate over known registries defined in settings.
96+
"""
97+
for registry in self.settings.registry:
98+
for filename in shpc.utils.recursive_find(registry):
99+
yield registry, filename
100+
93101
def guess_tag(self, module_name, allow_fail=False):
94102
"""
95103
If a user asks for a name without a tag, try to figure it out.
96104
"""
97105
if ":" in module_name:
98106
return module_name
99-
tags = os.listdir(os.path.join(self.settings.module_base, module_name))
107+
tags = self.installed_tags(module_name)
100108
if not tags and allow_fail:
101109
logger.exit("%s does not have any tags installed." % module_name)
102-
elif (tags or len(tags) > 1) and allow_fail:
110+
elif tags and len(tags) == 1:
111+
return "%s:%s" % (module_name, tags[0])
112+
elif tags and len(tags) > 1 and allow_fail:
103113
return
104-
elif len(tags) > 1:
105-
logger.exit(
106-
"Multiple tags found for %s: %s." % (module_name, ", ".join(tags))
107-
)
108-
else:
109-
module_name = "%s:%s" % (module_name, tags[0])
110-
return module_name
114+
115+
# Length of tags is > 1
116+
logger.exit("Multiple tags found for %s: %s." % (module_name, ", ".join(tags)))
117+
118+
def installed_tags(self, module_name):
119+
"""
120+
Get a list of installed tags.
121+
"""
122+
return os.listdir(os.path.join(self.settings.module_base, module_name))
111123

112124
def get_environment_file(self, module_name):
113125
"""

0 commit comments

Comments
 (0)