Skip to content
Snippets Groups Projects
update_gitlab_ci.py 4.46 KiB
Newer Older
#!/usr/bin/env python3
import argparse
import logging
import re


def update_gitlab_ci_include(filename, include_project, new_revision):
    """Update the include statement in a gitlab-ci yml to a given revision

    Parameters:
        filename( string): The path to the file to change.
        include_project( string): The path used to reference the project the include points to.
        new_revision (string): The hex sha to set the include to.

    Returns: True if the file was changed.
    """

    # Set the possible include in the local .gitlab.yml file
    # to the new revision. The include needs to have the revision
    # specified directly in the file, as it is parsed before the
    # submodule checkout is done

    # Use custom read write method as I didn't got ruamel yaml
    # to keep all costum formating (linebreaks ...)
    # This assumes following format of the include block
    # include:
    #   - project: 'SECO-Northern-Europe/yocto/infrastructure/gitlab-ci'
    #     ref: c5a3793e783fcb364c7f3bda73e8cd7c08a08804
    #     file: 'manifest-childs.yml'

    # Verify hash format:
    if re.match(r"\A[0-9a-fA-F]{40}\Z", new_revision) is None:
        raise TypeError("Format of specified revision is not correct")

    parsestate = 0
    changed = False

    # Remove the SECO-Northern-Europe part from the priject filter
    # as it is normally specified by $CI_PROJECT_ROOT_NAMESPACE
    include_project = include_project.split("/", 1)[1]
    logging.debug("Include project: %s", include_project)
    logging.debug("New revision: %s", new_revision)

    with open(filename, "r+", encoding="UTF-8") as fp:
        while True:
            linestart = fp.tell()
            line = fp.readline()
            parts = line.partition(":")
            logging.debug("Splitted input line: %s", parts)
            if len(line) == 0:
                break  # End of file
            if parsestate == 0:
                if parts[0] == "include":
                    # Found include block
                    parsestate = 1
                    logging.debug("Found 'include' block at %d", linestart)
            elif parsestate == 1:
                if parts[0] == "\n":
                    break  # End of include block

                if (
                    parts[0].endswith(" - project")
                    and parts[2].find(include_project) >= 0
                ):
                    # Found the correct project
                    parsestate = 2
                    logging.debug("Found 'project' entry at %d", linestart)
            elif parsestate == 2:
                if parts[0].endswith(" ref"):
                    # Found the ref: entry, compare the revision
                    logging.debug("Found 'ref' entry at %d", linestart)
                    parsestate = 1
                    if parts[2].find(new_revision) >= 0:
                        print(
                            "Revision in {} is already set to {}".format(
                                filename, new_revision
                            )
                        )
                    else:
                        print(
                            "Changed revision in {} to {}".format(
                                filename, new_revision
                            )
                        )
                        fp.seek(linestart)
                        fp.write("{}: {}".format(parts[0], new_revision))
                        fp.flush()
                        changed = True
                elif parts[0].find("- ") >= 0 or not parts[0].startswith("  "):
                    # Format of the line is not 'name: value' assume end of block
                    # Block was not found
                    break
    return changed


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--filename",
        help="""File to change""",
        required=True,
    )
    parser.add_argument(
        "--include-project",
        help="""The path to the included project as used in 'filename'""",
        required=True,
    )
    parser.add_argument(
        "--revision",
        help="""new revision for submodule""",
        required=True,
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action="store_true",
        help="""Increase verbosity.""",
    )

    args, _ = parser.parse_known_args()
    if args.verbose:
        logging.basicConfig(level=logging.DEBUG)

    update_gitlab_ci_include(args.filename, args.include_project, args.revision)


if __name__ == "__main__":
    main()