Commit 0f7cddd4 authored by Pierre Smeyers's avatar Pierre Smeyers
Browse files

Merge branch 'feat/support-alt-ci-file' into 'main'

feat: support lternative CI/CD config file with --alt-ci option

See merge request to-be-continuous/tools/gitlab-cp!103
parents ee6e526b 69bf52c5
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ dry-run-test:
    DEST_GITLAB_API: https://gitlab.com/api/v4
    DEST_SYNC_PATH: tbc-offtracks/to-be-continuous
    MAX_VISIBILITY: private
    ALT_CI: .gitlab-ci-namespaced.yml
    EXCLUDE: samples
    # DEST_TOKEN is declared as a project secret variable
  script:
+3 −1
Original line number Diff line number Diff line
@@ -19,7 +19,7 @@ gitlab-cp --help
usage: gitlab-cp [-h] [--src-api SRC_API] [--src-token SRC_TOKEN] [--src-sync-path SRC_SYNC_PATH] [--dest-api DEST_API] [--dest-token DEST_TOKEN] [--dest-sync-path DEST_SYNC_PATH] [--dest-keep-base-url DEST_KEEP_BASE_URL]
                 [--max-visibility {public,internal,private}] [--skip-visibility] [--exclude EXCLUDE] [--exclude-from EXCLUDE_FROM] [--include INCLUDE] [--include-from INCLUDE_FROM] [--insecure]
                 [--include-branch INCLUDE_BRANCH] [--exclude-branch EXCLUDE_BRANCH] [--update-release] [--deny-release-hash-change] [--update-avatar] [--no-group-description] [--no-project-description] [--new-group-options NEW_GROUP_OPTIONS]
                 [--new-group-options-from NEW_GROUP_OPTIONS_FROM] [--new-project-options NEW_PROJECT_OPTIONS] [--new-project-options-from NEW_PROJECT_OPTIONS_FROM] [--dry-run] [--halt-on-error]
                 [--new-group-options-from NEW_GROUP_OPTIONS_FROM] [--new-project-options NEW_PROJECT_OPTIONS] [--new-project-options-from NEW_PROJECT_OPTIONS_FROM] [--use-src-issue-tracker] [--alt-ci ALT_CI] [--dry-run] [--halt-on-error]
                 [--cache-dir CACHE_DIR]

This tool recursively copies/synchronizes a GitLab group from one GitLab server to another.
@@ -73,6 +73,7 @@ options:
                        a JSON file with extra options for projects creation; incompatible with --new-project-options
  --use-src-issue-tracker
                        make destination projects to use their source project issue tracker
  --alt-ci ALT_CI       alternative CI/CD configuration file(s) to prefer over '.gitlab-ci.yml', comma-separated and ordered by preference; if one of them exists in the source repository, it is set as the destination project's CI/CD configuration file
  --dry-run             dry run (don't execute any write action)
  --halt-on-error       halt synchronizing whenever an error occurs
  --cache-dir CACHE_DIR
@@ -107,6 +108,7 @@ options:
| `--new-project-options`      | `$NEW_PROJECT_OPTIONS`          | a JSON string with [extra options for projects creation](https://docs.gitlab.com/api/projects/#create-a-project) (default value disables issues and MR, *see below*)                                                                                     |
| `--new-project-options-from` | `$NEW_PROJECT_OPTIONS_FROM`     | a JSON file with [extra options for projects creation](https://docs.gitlab.com/api/projects/#create-a-project); incompatible with `--new-project-options`                                                                                                |
| `--use-src-issue-tracker`    | `$USE_SRC_ISSUE_TRACKER`        | make destination projects to use their source project issue tracker                                                                                                                                                                                      |
| `--alt-ci`                   | `$ALT_CI`                       | alternative CI/CD configuration file(s) to prefer over `.gitlab-ci.yml` (multiple CLI option; env. var. is a comma-separated list, ordered by preference); if one exists in the source repository, it is set as the destination project's CI/CD configuration file; *Example: `.gitlab-ci-alt.yml`* |
| `--dry-run`                  | `$DRY_RUN`                      | dry run (don't execute any write action)                                                                                                                                                                                                                 |
| `--halt-on-error`            | `$HALT_ON_ERROR`                | halt synchronizing when an error occurs                                                                                                                                                                                                                  |
| `--cache-dir`                | `$CACHE_DIR`                    | cache directory (used to download resources such as images and Git repositories) (defaults to `.work`)                                                                                                                                                   |
+68 −1
Original line number Diff line number Diff line
@@ -83,6 +83,7 @@ class Synchronizer:
        new_project_options={},
        new_group_options={},
        use_src_issue_tracker=False,
        alt_ci: Optional[list[str]] = None,
    ):
        self.src_client = src_client
        self.src_sync_path = src_sync_path
@@ -111,6 +112,7 @@ class Synchronizer:
        self.new_project_options = new_project_options
        self.new_group_options = new_group_options
        self.use_src_issue_tracker = use_src_issue_tracker
        self.alt_ci = alt_ci or []

    def handle_error(self, err: Exception) -> None:
        self.errors.append(err)
@@ -617,6 +619,58 @@ class Synchronizer:
            f"    - default branch: {AnsiColors.HYELLOW}updated{AnsiColors.RESET} ({src_project.default_branch})"
        )

    # Synchronize the CI/CD configuration file path of a project
    # If one of the alternative files (--alt-ci) exists in the source repository,
    # set it as the project's CI/CD configuration file (preferred over .gitlab-ci.yml).
    def sync_ci_config(self, src_project: Project, dest_project: Project) -> None:
        if not src_project.default_branch:
            # repository not initialized: nothing to do
            return

        # find first alternative CI config file present in the source repository
        prefered_ci: Optional[str] = "" # empty value means '.gitlab-ci.yml'
        for path in self.alt_ci:
            try:
                src_project.files.get(file_path=path, ref=src_project.default_branch)
                prefered_ci = path
                break
            except GitlabGetError as ge:
                if ge.response_code != 404:
                    print(
                        f"    - CI/CD config file: lookup {AnsiColors.HRED}failed{AnsiColors.RESET} ({path})",
                        ge,
                    )
                    self.handle_error(ge)
                    return

        # GitLab default (empty value means '.gitlab-ci.yml')
        current_ci = dest_project.ci_config_path or ""

        if current_ci == prefered_ci:
            print(
                f"    - CI/CD config file: {AnsiColors.HGRAY}up-to-date{AnsiColors.RESET} ({prefered_ci or '.gitlab-ci.yml'})"
            )
            return

        if self.dry_run:
            print(
                f"    - CI/CD config file: {AnsiColors.HYELLOW}update needed{AnsiColors.RESET} ({current_ci or '.gitlab-ci.yml'} -> {prefered_ci or '.gitlab-ci.yml'})"
            )
            return

        try:
            dest_project.ci_config_path = prefered_ci
            dest_project.save()
            print(
                f"    - CI/CD config file: {AnsiColors.HYELLOW}updated{AnsiColors.RESET} ({prefered_ci or '.gitlab-ci.yml'})"
            )
        except GitlabUpdateError as ge:
            print(
                f"    - CI/CD config file: update {AnsiColors.HRED}failed{AnsiColors.RESET} ({prefered_ci})",
                ge,
            )
            self.handle_error(ge)

    # Synchronizes a GitLab project
    # $1: source project JSON
    # $2: destination parent group ID (number)
@@ -744,7 +798,10 @@ class Synchronizer:
        # 4: sync Default branch
        self.sync_default_branch(src_project, dest_project)

        # 5: sync Releases
        # 5: sync CI/CD configuration file path
        self.sync_ci_config(src_project, dest_project)

        # 6: sync Releases
        self.sync_releases(src_project, dest_project)

    # Synchronizes recursively a GitLab group
@@ -1113,6 +1170,12 @@ def run() -> None:
        default=trueish_env_var("USE_SRC_ISSUE_TRACKER"),
        help="make destination projects to use their source project issue tracker",
    )
    parser.add_argument(
        "--alt-ci",
        action="append",
        default=x.split(",") if (x := os.getenv("ALT_CI")) else [],
        help="alternative CI/CD configuration file(s) to prefer over '.gitlab-ci.yml', comma-separated and ordered by preference; if one of them exists in the source repository, it is set as the destination project's CI/CD configuration file",
    )
    parser.add_argument(
        "--dry-run",
        default=trueish_env_var("DRY_RUN"),
@@ -1287,6 +1350,9 @@ def run() -> None:
    print(
        f"- use src iss. tracker (--use-src-issue-tracker): {AnsiColors.CYAN}{args.use_src_issue_tracker}{AnsiColors.RESET}"
    )
    print(
        f"- alt CI config files (--alt-ci)                : {AnsiColors.CYAN}{', '.join(args.alt_ci)}{AnsiColors.RESET}"
    )
    print()

    # TODO: configurable
@@ -1332,6 +1398,7 @@ def run() -> None:
        new_group_options=new_group_options,
        new_project_options=new_project_options,
        use_src_issue_tracker=args.use_src_issue_tracker,
        alt_ci=args.alt_ci,
    )
    # retrieve src root group
    try: