Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement singularity-compose.yml override configuration feature #48

Merged
merged 27 commits into from
Oct 7, 2021

Conversation

PauloMigAlmeida
Copy link
Contributor

There is one feature in docker-compose that I would love to have in singularity-compose that is the ability to specify multiple composer.yml files in which the properties would get either merged or overidden.

Example:

singularity-compose.yml

version: "2.0"
instances:

  cvat:
    image: cvat.sif
    start:
      options:
        - containall
    volumes:
      - ./recipes/cvat/env.sh:/.singularity.d/env/env.sh
      - ./volumes/cvat/data:/home/django/data
      - ./volumes/cvat/keys:/home/django/keys
      - ./volumes/cvat/logs:/home/django/logs
      - ./volumes/cvat/logs:/var/log/supervisor
      - ./volumes/cvat/tmp:/tmp
    network:
      enable: false

singularity-compose-dev.yml

version: "2.0"
instances:

  cvat:
    start:
      options:
        - containall
        - fakeroot

  cvatproxy:
    image: cvatproxy.sif
    start:
      options:
        - containall
    volumes:
      - ./volumes/cvatproxy/cache/nginx:/var/cache/nginx
      - ./volumes/cvatproxy/run:/var/run/
      - ./volumes/cvatproxy/log:/var/log/nginx
      - ./volumes/cvatproxy/lib:/var/lib/nginx
    network:
      enable: false
    depends_on:
      - cvat

By executing something like:

singularity-compose -f singularity-compose.yml -f singularity-compose.dev.yml <any_command>

we would get the runtime config combined into:

version: "2.0"
instances:

  cvat:
    image: cvat.sif
    start:
      options:
        - containall
        - fakeroot
    volumes:
      - ./recipes/cvat/env.sh:/.singularity.d/env/env.sh
      - ./volumes/cvat/data:/home/django/data
      - ./volumes/cvat/keys:/home/django/keys
      - ./volumes/cvat/logs:/home/django/logs
      - ./volumes/cvat/logs:/var/log/supervisor
      - ./volumes/cvat/tmp:/tmp
    network:
      enable: false

  cvatproxy:
    image: cvatproxy.sif
    start:
      options:
        - containall
    volumes:
      - ./volumes/cvatproxy/cache/nginx:/var/cache/nginx
      - ./volumes/cvatproxy/run:/var/run/
      - ./volumes/cvatproxy/log:/var/log/nginx
      - ./volumes/cvatproxy/lib:/var/lib/nginx
    network:
      enable: false
    depends_on:
      - cvat

which essentially overrode cvat->start->options and appended/merged cvatproxy to the runtime config.

I'm looking for feedback on the following points

  1. whether or not you agree this is a feature that you want to see in singularity-compose?
  2. I did a draft implementation, any thing that you want me to change or implement differently?

Copy link
Member

@vsoch vsoch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very cool idea! We could definitely support this. I've added some comments because we need to:

  • support previous functionality (e.g., the user providing no -f defaults to a single singularity-compose.yml as before)
  • the Python client in project.py also needs to work the same, still accepting a string (and you are extending to allow a list to merge).
  • The merge / load multiple yaml should not live in project.py - you can create a new module for it until utils if that's appropriate!

@@ -64,8 +64,8 @@ def set_filename(self, filename):
==========
filename: the singularity-compose.yml file to use
"""
self.filename = filename or "singularity-compose.yml"
self.working_dir = os.path.dirname(os.path.abspath(self.filename))
self.filename = filename
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would leave the default here to "singularity-compose.yml" - and if you are changing this to require a list, then please support the old behavior to still provide a single filename, and have it checked if it's a list, single string, or None, and process accordingly. And then if your preference is to store in a list, the variable should be self.filenames to indicate plural / multiple files.

And if I understand correctly, the working directory is changed to os.getcwd() to account for possibly different compose files in different directories. Of course the issue here is that all compose files need to then have their context relevant to that directory (is that your intention?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would leave the default here to "singularity-compose.yml"

The reason why I removed it is because this default value comes from the argparse as well, so I thought it was redundant. Quick question, the reason why you want the default value here as well is because you are envisioning devs instantiating this class programatically instead of using the CLI, is that correct?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes correct! We wouldn't want to break previous scripts that are providing a string. The simple solution I think is to allow either.

for f in self.filename:
# ensure file exists
if not os.path.exists(f):
bot.error("%s does not exist." % f)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move the multiple load of yaml files outside of the project.py - there should ideally be a separate utils function or set of config functions (some config.py or similar) that takes the list. and returns the single config here. Also - self.filename implies one file, and there should either be a self.filenames or self.filename to match the arity.

Comment on lines 164 to 178
def deep_merge(self, yaml_files):
"""merge singularity-compose.yml files into a single dict"""
if len(yaml_files) == 1:
# nothing to merge as the user specified a single file
return yaml_files[0]

base_yaml = None
for idx, item in enumerate(yaml_files):
if idx == 0:
base_yaml = item
else:
base_yaml = self.merge(base_yaml, item)

return base_yaml

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of this logic should not be in project.py - it's for merging yaml.


return base_yaml

def merge(self, a, b):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this too.

@vsoch
Copy link
Member

vsoch commented Oct 5, 2021

Also - if we provide this functionality I'd add an ability to preview a merge, basically taking the files, doing the merge, and printing yaml back to the screen.

@PauloMigAlmeida
Copy link
Contributor Author

PauloMigAlmeida commented Oct 5, 2021

Also - if we provide this functionality I'd add an ability to preview a merge, basically taking the files, doing the merge, and printing yaml back to the screen.

Do you mean like a CLI option similar to what --read_only is ? Or printing it to console with (let's say logging verbosity set to debug)?

@vsoch
Copy link
Member

vsoch commented Oct 5, 2021

Do you mean like a CLI option similar to what --read_only is ? Or printing it to console with (let's say logging verbosity set to debug)?

It could be that we have a separate command group for the config, and then something like a --preview? It doesn't have to match docker-compose exactly, although if docker compose already has that functionality we could. E.g.,:

# If we still use the up command group
$ singularity-compose up -d --preview

# A more general validation function (that could validate...
$ singularity-compose config --check

# And then support a preview
$ singularity-compose config --check

We can definitely split this into two different PRs - the reason I mentioned it is because not having the final file in a repository, for example, hinders reproducibility. So minimally I think we'd want to be able to output it to slightly better encourage saving it :)

@PauloMigAlmeida
Copy link
Contributor Author

PauloMigAlmeida commented Oct 6, 2021

Hi @vsoch, apologies for asking one more question on that subject, I just want to make sure I understood what needs to be done.

Currently, if we run singularity-compose -f file1 -f file2 config this will already print the interpolated version (if multiple files are specified) or the original file (if just a single one is specified). The only caveat with that command is that it prints the content in JSON instead of YAML but given that its contents is interchangeable then that all good.

(that's the part which I'm not 100% sure if I got right, please correct me where I'm wrong):
If I understood you correctly, you want singularity-compose config --check to do pretty much the same thing but printing it in YAML format so that would slightly encourage devs to save the content.

If the above is not correct, I am not sure if I got the --check option's purpose 😬

@vsoch
Copy link
Member

vsoch commented Oct 6, 2021

Yes sorry I'm not being clear!

(that's the part which I'm not 100% sure if I got right, please correct me where I'm wrong):
If I understood you correctly, you want singularity-compose config --check to do pretty much the same thing but printing it in YAML format so that would slightly encourage devs to save the content.

So my higher level idea in adding this check (akin to black --check for formatting) is to check the formatting / correctness of the files. To do this would be fairly simple - we'd add json schema (which I noticed we don't have here!) to do the check, and then the return code would be 0 all is good, 1 there was an error. So:

# Tell me if my config file(s) look okay (run against json schema)
$ singularity-compose config --check
$ echo $?
# 0 indicates everything looks okay

# Without --check we _could_ do a preview, but for one config this wouldn't make sense, so I'd suggest `--preview`
$ singularity-compose config --preview

which would then print the consolidated config to the screen. Perhaps for one config it would show default options that aren't explicitly stated.

To be clear, adding a command group is a big thing, and I'd be happy to help with this! If you want to focus on working on the merge functionality (which you've already done most of, just need to move it around to a different spot) I'd be happy to extend that PR this weekend to get the config command group to preview results, and it would be logical to add tests.

The only caveat with that command is that it prints the content in JSON instead of YAML but given that its contents is interchangeable then that all good.

We can easily yaml.dump the json to see the yaml instead! The user might want to dump (and save) the new compiled yaml file.

@PauloMigAlmeida
Copy link
Contributor Author

PauloMigAlmeida commented Oct 6, 2021

thanks for the clarification, I got it now :)

Sure, feel free to commit stuff to this PR. For now, I will focus on adding the tests and docs related to this feature before taking on any of the other stuff.

@vsoch
Copy link
Member

vsoch commented Oct 6, 2021

Sounds good! And I can definitely help too - I've done a bit of work with jsonschema so it's pretty quick for me to do.

@PauloMigAlmeida
Copy link
Contributor Author

Your help is always welcome 😃

@PauloMigAlmeida
Copy link
Contributor Author

I moved the logic out of project, added tests and docs. I will mark this PR as 'ready to review' just in case you want to expand those functionalities in a different PR.

@PauloMigAlmeida PauloMigAlmeida marked this pull request as ready for review October 6, 2021 04:32
this will add jsonschema as an optional dependency to check the validity of a
singularity-compose.yml file. We will eventually extend this to check (and preview)
combined files.

Signed-off-by: vsoch <vsoch@users.noreply.github.com>
@vsoch
Copy link
Member

vsoch commented Oct 6, 2021

Here is what I had in mind for check! #49

So if we add a --preview to that given two combined files (as you've done here) we can print the final thing to the console.

@vsoch
Copy link
Member

vsoch commented Oct 6, 2021

Just saw your last message - I can pull down your changes into my branch and make a combined PR - not tonight but probably another night / this weekend (unless you beat me to it)

@PauloMigAlmeida
Copy link
Contributor Author

Nice! I liked your implementation for check, I can implement the the --preview tomorrow =)

@vsoch
Copy link
Member

vsoch commented Oct 6, 2021

And I am aware of the comedic value of failing my own lint check! I think it's probably time to remove the version constraint for black, and assume that most people will create a new venv to have it installed freshly. 😆

@PauloMigAlmeida
Copy link
Contributor Author

A bit off-topic (in relation to this PR) but something that it would be nice for us to start thinking before writing any code... another thing I would love to have on singularity-compose is to be able to create volumes and reference them by name rather than path.

Usually docker create these persistent volumes under $HOME/.docker and I was thinking about using a path along the same train of thought.

I cannot count how many times I've (accidentally) deleted my 'persistent' paths before as I'm always tempted to have those mapped folders within my project directory structure 😬 There are other cases in which this is relevant (such as file system capabilities) but the key point is, if we could start thinking about this now, we can get a nice implementation shortly 😄

@PauloMigAlmeida
Copy link
Contributor Author

Moved that to an issue to make sure the 2 subjects won't get mixed (I should've thought about that before 😓 )

@PauloMigAlmeida
Copy link
Contributor Author

@vsoch

I pulled your check feature PR (and tweaked it so it could take into consideration multiple files. I also implemented implemented the --preview argument. Let me know if there is anything missing :)

Copy link
Member

@vsoch vsoch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beautiful documentation, and tests too! Am I in OSS heaven?

I added a few changes, and once those are in I'll test locally!

docs/commands.md Outdated Show resolved Hide resolved
docs/commands.md Outdated Show resolved Hide resolved
docs/commands.md Outdated Show resolved Hide resolved
docs/commands.md Outdated Show resolved Hide resolved
docs/commands.md Outdated Show resolved Hide resolved
docs/commands.md Outdated Show resolved Hide resolved
docs/commands.md Outdated Show resolved Hide resolved
scompose/project/project.py Outdated Show resolved Hide resolved
scompose/project/project.py Outdated Show resolved Hide resolved
scompose/utils/__init__.py Outdated Show resolved Hide resolved
PauloMigAlmeida and others added 2 commits October 7, 2021 13:18
Co-authored-by: Vanessasaurus <814322+vsoch@users.noreply.github.com>
@PauloMigAlmeida
Copy link
Contributor Author

Done, feel free to pull it locally and run your tests :)

Copy link
Member

@vsoch vsoch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few more comments after local testing!

  1. See updates to printing logic in the main function of check,py - basically removing extra output so it's a little cleaner and we only get what we ask for w.r.t the check command.
  2. for the tests folder, please rename config_override to config_merge to match what we are doing.

And that should do it!

docs/commands.md Outdated Show resolved Hide resolved
scompose/client/check.py Outdated Show resolved Hide resolved
docs/commands.md Outdated Show resolved Hide resolved
@PauloMigAlmeida
Copy link
Contributor Author

Done, I also moved the tests belonging to this feature to a new file since we moved it out of the utils module

scompose/client/check.py Outdated Show resolved Hide resolved
Signed-off-by: vsoch <vsoch@users.noreply.github.com>
and moving validate.py into its own file so we do not always import

Signed-off-by: vsoch <vsoch@users.noreply.github.com>
Signed-off-by: vsoch <vsoch@users.noreply.github.com>
@vsoch vsoch self-requested a review October 7, 2021 02:57
@vsoch vsoch merged commit 55db827 into singularityhub:master Oct 7, 2021
@vsoch
Copy link
Member

vsoch commented Oct 7, 2021

This is such a great feature - thank you @PauloMigAlmeida !

@PauloMigAlmeida PauloMigAlmeida mentioned this pull request Feb 28, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants