Editing Docker-compose files with Python

written 2020-06-14

Docker-Compose files declare the state of a Docker stack and are used to configure Docker Containers. In this Blog I will show you how to edit them with Python, so you can change them programmatically.

Packages for editing YAML

Docker-Compose files are written in YAML, but unlike JSON Pythons Standard Library does not come with a parser for YAML. But there are some in the Python Package Index. My first instinct was PyYAML but I does come with a downside: It sorts the keys alphabetically in the output. This means that the Version of the Docker-Compose file would be at the bottom of the file. This behaviour is can be changed in Version 5.1 of PyYAML: yaml.dump(data, default_flow_style=False, sort_keys=False).

There is also ruamel.yaml which saves some roundtrips while emitting YAML files and thus leaves the structure of the file mostly intact. Thus I decided to give it a try.

Example: Changing the Version of an Image

This example shows how to change the Version of a Docker Image with a little commandline script. The script takes a path and a version as positional arguments

import argparse
import os
import ruamel.yaml
yaml = ruamel.yaml.YAML()
yaml.preserve_quotes = True
yaml.indent(sequence=3, offset=1)

def get_docker_compose_files(path):
    """
    Walks the given path and collects all docker-compose.* files and yields
    """
    for dirpath, dirnames, files in os.walk(path):
        for file in files:
            if 'docker-compose.' in file:
                yield os.path.join(dirpath, file) 
     

def change_version(version, path):
    """
    Changes the Version of the Example Service
    Note: Example could be a good candidate for an additional commandline
    argument
    """
    docker_compose_files = get_docker_compose_files(path)
    for docker_compose in docker_compose_files:
        with open(docker_compose, 'r') as ymlfile:
            docker_config = yaml.load(ymlfile)
            image = docker_config['services']['example']['image']
            string_split = image.split(':')
            string_split.pop()
            string_split.append(version)
            new_image_string = ':'.join(string_split)
            docker_config['services']['example']['image'] = new_image_string
        with open(docker_compose, 'w') as newconf:
            yaml.dump(docker_config, newconf)


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("version")
    parser.add_argument('path')
    args = parser.parse_args()
    version = args.version
    path = args.path
    change_version(version, path)

if __name__ == '__main__':
    main()

Conclusion

The resulting docker-compose file will look almost the same as the input file and this keeps standard conventions like devlaring the version first and the networks or volumes last.

There is no comment system. If you want to contact me about this article, you can do so via e-mail or Mastodon.