DEV Community

Wen Ping
Wen Ping

Posted on Originally published at anguskit.com

Move GitLab CI to the built-in pipeline: how to rewrite the YAML

This article explains how to turn .gitlab-ci.yml into an AngusGit pipeline with Import GitLab CI, and what to change before you save. After reading it, you can use the unmapped list and the notes to handle artifacts, rules, and when that did not convert, add the branch limit back, and keep deploy from running when nobody has clicked. You create any credentials yourself.

Pipelines are available on Team and Enterprise. Community has no CI/CD menu. If you have not written an AngusGit pipeline yet, read the YAML getting-started article linked at the end first. A job is one group of steps in a pipeline. A runner is the program on a build machine that picks up a job and runs the commands.

1. A .gitlab-ci.yml used for the conversion

The file below is only for showing the conversion. It is not a file from a live repository. It has three stages: build, then test, then a deploy on main that a person starts by hand.

stages:
  - build
  - test
  - deploy

variables:
  MAVEN_OPTS: "-Dmaven.repo.local=.m2"

build:
  stage: build
  image: maven:3.9-eclipse-temurin-21
  tags: [docker]
  script:
    - mvn -B -DskipTests package
  artifacts:
    paths: [target/*.jar]

test:
  stage: test
  image: maven:3.9-eclipse-temurin-21
  tags: [docker]
  script:
    - mvn -B test
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - if: '$CI_COMMIT_BRANCH == "main"'

deploy:
  stage: deploy
  tags: [deploy]
  script:
    - ./deploy.sh $CI_COMMIT_SHORT_SHA
  when: manual
  only: [main]
Enter fullscreen mode Exit fullscreen mode

2. Import in the console and take the draft

  • Open the import dialog

Open CI/CD → Pipelines and click New Pipeline. The menu has Blank pipeline, Generate from repository, Import GitHub Actions, and Import GitLab CI. Under the last two, the hint is “Some syntax is converted automatically — review after import”. Choose Import GitLab CI.

  • Paste the file and convert

The dialog title is Import GitLab CI. The description is “Maps a subset to AngusCICD YAML. include, full rules, and child pipelines are not expanded — check the unmapped list.” The default tab is Paste YAML (the other tab is Scan repository). The field label is GitLab CI, and the placeholder is “Paste .gitlab-ci.yml”. Paste the whole YAML from section 1 and click Convert.

  • Read the two lists

The result sits under the input, in this order: Unmapped (needs a manual edit), Notes, Pipeline name, then the YAML editor. The pipeline name is filled in as imported-from-gitlab (the placeholder is “Enter pipeline name”).

  • Create only after the lists are checked

Opened from the pipeline list, the button at the lower right is Create Pipeline. Opened from the blank create page, the same place is Apply to editor. If the list items are still unfixed, click Cancel. The repository keeps no pipeline.

Import GitLab CI result: four unmapped lines, three notes, pipeline name imported-from-gitlab

After convert, the middle of the dialog scrolls to the two lists. The paste box above may scroll out of view. The title, the description, and the lower-right Cancel and Create Pipeline stay in place. The four unmapped lines are:

  • jobs.build.artifacts
  • jobs.test rules/only/except (used only to infer the trigger; the full condition was not mapped)
  • jobs.deploy rules/only/except (used only to infer the trigger; the full condition was not mapped)
  • jobs.deploy.when

That line means the rules were used only to guess the trigger, and the full condition was not mapped.

The three notes are:

  • jobs.deploy has no image, mapped to NATIVE
  • CI_COMMIT_SHORT_SHA → commitSha (full SHA, not the short SHA)
  • GitLab CI_* predefined variables were rewritten approximately; other CI_* variables are not injected at runtime

Those notes mean: deploy has no image and is mapped to NATIVE; CI_COMMIT_SHORT_SHA becomes commitSha, which is the full SHA, not a short SHA; GitLab CI_* variables were rewritten approximately, and any remaining CI_* names are not injected at run time.

NATIVE in the first note, and DOCKER in the draft below, are the two execution modes. DOCKER runs the commands in a container started from the image you name. NATIVE runs them directly on the machine where the runner process lives. Items that converted cleanly are not listed one by one. The draft in the editor is:

version: '1'
name: imported-from-gitlab
triggers:
- 'on': push
- 'on': pull_request
env:
  MAVEN_OPTS: -Dmaven.repo.local=.m2
pipeline:
  build:
    runner:
      mode: DOCKER
      image: maven:3.9-eclipse-temurin-21
      labels:
      - docker
    steps:
    - name: script
      run: mvn -B -DskipTests package
  test:
    runner:
      mode: DOCKER
      image: maven:3.9-eclipse-temurin-21
      labels:
      - docker
    steps:
    - name: script
      run: mvn -B test
    needs:
    - build
  deploy:
    runner:
      mode: NATIVE
      labels:
      - deploy
    steps:
    - name: script
      run: ./deploy.sh ${{ commitSha }}
    needs:
    - test
Enter fullscreen mode Exit fullscreen mode

'on' is quoted because YAML treats an unquoted on as a boolean. The exporter adds the quotes on purpose. This is the converted draft. The unmapped items still need a decision before you save.

Left: the original config. Right: the draft. deploy still runs on its own

3. What converted, and what did not

Start with the parts that convert. The important one is stages. GitLab CI uses stages for order. AngusGit has no stages. The later job writes needs to name the job it waits for.

Import converts these on its own:

GitLab CI How it looks in the draft Notes
script Step name script, command in run A one-line script gets the step name script
image runner.mode: DOCKER plus runner.image The image name is kept as maven:3.9-eclipse-temurin-21
No image runner.mode: NATIVE For example deploy. The note is jobs.deploy has no image, mapped to NATIVE
tags runner.labels docker and deploy become labels as written
variables Pipeline-level env MAVEN_OPTS is written in the top-level env
stages The job in the next stage writes needs test depends on build, and deploy depends on test. stages itself is not kept
A merge request inside rules The triggers gain a pull_request entry The full if is not written into the job
allow_failure continue-on-error This sample does not use it
environment Only the environment name is kept This sample does not use it. When it is present, the note is keeps only the name, without GitLab environment protection, meaning only the name is kept and GitLab environment protection rules are not

Next are the parts that did not convert. The table lists them, and the bullets after the table spell each one out.

This sample did not convert these. Handle them before you save:

What the list shows What the draft actually did What to change before you save
jobs.build.artifacts target/*.jar is not passed to later jobs You can leave this sample as it is for now
rules / only on jobs.test and jobs.deploy Used only to guess the trigger. main did not become branches Add branches: [main] on the triggers
jobs.deploy.when when: manual was dropped. deploy is still there with needs: [test] Remove deploy and create another pipeline, or add approval
include, retry, timeout, dependencies, trigger Absent from this sample. When they appear, they go into the unmapped list See the notes after the table
  • artifacts (build outputs): In this sample, test runs mvn test again and does not read that jar, so the two jobs can stay as they are. If a later script must read a file from the previous job, put those steps in the same job, or upload the file to an artifact repository and download it later.
  • rules / only: In the draft, both push and pull_request have no branches: [main]. Other branches trigger this pipeline, and deploy runs with them.
  • when: After when: manual is dropped, deploy runs on every trigger. To keep deploy from starting on its own, remove deploy from this pipeline and create another pipeline triggered by workflow_dispatch (manual run only), or add approval on this job.
  • Other fields: If the file uses include, merge the referenced pieces into one YAML before you import. Rewrite a job-level retry or timeout onto the step, in seconds. Split a downstream pipeline started by trigger and handle it on its own.

A schedule can be imported, and you fill in the cron yourself. A cron expression describes when a schedule fires. GitLab stores the schedule on the page, not in the YAML. If the YAML has only: schedules, or a rules condition that mentions schedule, import adds on: schedule and writes this unmapped line: schedule cron was not mapped (GitLab schedules live in the UI, not in the YAML). That means the cron is not mapped, because the GitLab schedule lives in the UI, not in the YAML. You then add the cron yourself, in five fields, for example 0 2 * * *. Section 1 has no schedule, so the draft has no on: schedule.

4. Check CI_* variables one by one

GitLab injects a set of predefined variables whose names start with CI_. AngusGit does not inject them. Import rewrites only the names in the table below into ${{ }} expressions. It does not turn them into the environment variable GIT_COMMIT_SHA:

GitLab variable After import In this sample
$CI_COMMIT_SHA ${{ commitSha }} The sample script does not use it
$CI_COMMIT_SHORT_SHA Also ${{ commitSha }}, the full SHA The deploy command becomes ./deploy.sh ${{ commitSha }}
$CI_COMMIT_BRANCH ${{ branch }} Only inside the test rules. That if is not written into the job
$CI_PIPELINE_SOURCE ${{ event }} Used only to infer pull_request
$CI_COMMIT_TAG ${{ ref }} Absent here. When it appears, the note is CI_COMMIT_TAG → ref (approximate), an approximate mapping

$CI_COMMIT_SHORT_SHA is the easy one to get wrong. The note is CI_COMMIT_SHORT_SHA → commitSha (full SHA, not the short SHA): after the rewrite you get the full commit id. For an 8-character short SHA, cut $GIT_COMMIT_SHA in the shell yourself. ${{ commitSha }} is the full value.

Other CI_ variables outside the table, such as CI_PROJECT_DIR, CI_PIPELINE_ID, and CI_REGISTRY, are not injected at run time. When the original file had a variable rewritten and still contains a CI_ variable, the notes include this sentence: GitLab CI_* predefined variables were rewritten approximately; other CI_* variables are not injected at runtime. A script you write can use the platform environment variables GIT_REPO_URL, GIT_BRANCH, and GIT_COMMIT_SHA.

One more group of names needs a look. When a name ends with TOKEN, SECRET, PASSWORD, PASSWD, PRIVATE_KEY, ACCESS_KEY, or API_KEY, import writes that name into the job secrets list and adds this note: $NAME → env.NAME, and added to the job secrets list. NAME is the variable name. The note means the name is rewritten to env.<name> and added to the job secrets list. AngusGit then needs a credential with the same name. Otherwise the job fails with Credential not visible or expired. You create the credential yourself under CI/CD → Settings → Credentials. Section 1 has no such variable, so the draft has no secrets.

5. A shape worth saving

This draft is not ready to save. Two places say the opposite of the original file. only: [main] did not become branches: [main], so other branches trigger it. when: manual is gone, so deploy still runs automatically after test.

Split it into two pipelines. The first keeps build and test, and adds the branch limit. Keep the converted image for now. If the private network cannot pull Docker Hub, replace maven:3.9-eclipse-temurin-21 with the platform image repo.anguskit.com/docker-public/angus-ci-maven:21. The YAML below is the hand-edited result, not the draft the importer produced:

version: "1"
name: imported-from-gitlab
triggers:
  - "on": push
    branches: [main]
  - "on": pull_request
    branches: [main]
env:
  MAVEN_OPTS: "-Dmaven.repo.local=.m2"
pipeline:
  build:
    runner:
      mode: DOCKER
      image: maven:3.9-eclipse-temurin-21
      labels: [docker]
    steps:
      - name: script
        run: mvn -B -DskipTests package
  test:
    needs: [build]
    runner:
      mode: DOCKER
      image: maven:3.9-eclipse-temurin-21
      labels: [docker]
    steps:
      - name: script
        run: mvn -B test
Enter fullscreen mode Exit fullscreen mode

needs only sets the order. It does not pass target/*.jar to test. These can stay as two jobs because test runs mvn test itself and does not need that jar. If the deploy script must read the jar, write package and deploy as two steps in the same job.

The second pipeline holds only deploy, and it can be started only by hand. ${{ commitSha }} in the script argument is still the full SHA. A workable shape is:

version: "1"
name: deploy
triggers:
  - "on": workflow_dispatch
pipeline:
  deploy:
    runner:
      mode: NATIVE
      labels: [deploy]
    steps:
      - name: script
        run: ./deploy.sh "${{ commitSha }}"
Enter fullscreen mode Exit fullscreen mode

If deploy should still follow the build, and a named person must approve it before it continues, add approval on that job. The dropped when: manual does not do that. The approval article linked at the end shows how.

6. Check once before you really save

After the edits, create the pipeline and check it in three steps:

  • Pass the pre-run check

If the dialog was opened from the list, click Create Pipeline. If it was opened from the blank create page, click Apply to editor, then save on the create page. Before save, the platform runs a pre-run check. When it passes, the page shows “All checks passed. Ready to save.” When it shows “Fix errors first”, change the YAML as the message says. The usual cause is a GitLab field such as stages or variables still left in the YAML.

  • Run it once by hand

On the pipeline detail page, click Trigger, fill Branch / tag with a test branch, and confirm that a runner with the docker label picks up the job. If deploy is still in the same pipeline, this run really executes the deploy command.

  • Then check the automatic trigger

After the manual run passes, open a pull request. Confirm that pull_request triggers the pipeline and that the result is written back to the status check on the pull request page (the row that shows whether the pipeline passed).

During the move, .gitlab-ci.yml can stay in the repository. AngusGit does not run it. Delete it after the new pipeline has been running steadily.

Common questions

These are the situations people hit most often after the move:

What you see Why What to do
The job stays queued No online runner matches both mode and labels The runner needs the docker label and must be able to run that execution mode
The job is not in a container and runs on the host The original job had no image, so it became NATIVE Add runner.mode: DOCKER and runner.image
A later job cannot find target/*.jar artifacts is unmapped, and needs does not pass files Put the steps in one job, or pass the file through an artifact repository
Deploy runs when nobody clicked when: manual is unmapped, and the job is still in the pipeline Remove that job, or switch it to workflow_dispatch / approval
A branch other than main also deploys only: [main] was not written as branches Add branches: [main] on push and pull_request
A path or an id in the script is empty The script uses a CI_* variable that is not injected Replace it as in section 4. Cut a short SHA from $GIT_COMMIT_SHA yourself
There is no automatic build after the merge into main Merging on the pull request page does not fire on: push. Only git push does Use pull_request to run build and test before the merge

Can I import several files at once?

No. The dialog handles one YAML at a time. If the project uses include (one YAML pulls in other YAML pieces), merge the referenced files into one file before you import. Otherwise the unmapped list says include (remote or local snippets are not fetched; expand them before import), meaning remote and local fragments are not fetched and must be expanded before import.

Can I use the conversion result as it is?

The draft of this sample is not ready. deploy runs on its own, and the branch limit is gone. Fix those two places as in section 5, then save.

Summary

Paste .gitlab-ci.yml into Import GitLab CI and click Convert. You get a draft that still needs a person to check it. In this example the name is imported-from-gitlab. script, image, tags, variables, and stages become steps, DOCKER mode, labels, env, and needs. For section 1, the unmapped items are jobs.build.artifacts, two rules/only/except lines, and jobs.deploy.when. The notes say deploy became NATIVE, and $CI_COMMIT_SHORT_SHA became the full ${{ commitSha }}. The important part: when: manual does not become a manual job, and only: [main] does not become a branch limit. Left unchanged, deploy runs automatically on other branches. Check each item, edit, then create the pipeline. Create the credentials you need yourself.

Conclusion: Import GitLab CI gives you only the converted draft. Before you save, handle the unmapped artifacts and when, turn the dropped only: [main] into branches: [main] on the triggers, and remember that ${{ commitSha }} is the full SHA.

Further reading

Top comments (0)