From 10ef9d68018ddd9b001499cb0bbb72ce1cffb4da Mon Sep 17 00:00:00 2001
From: Thierry Martinez <Thierry.Martinez@inria.fr>
Date: Wed, 15 Feb 2023 17:31:42 +0100
Subject: [PATCH] Fix #2: Unique state and runner for a given pipeline

This commit makes state filename unique for a given pipeline (and
remove this state in the cleaning phase), and adds a unique tag to the
runners created for the pipeline for jobs not to be run in runners
from other pipelines that run in parallel.
---
 .gitlab-ci.yml             | 72 +++++++++-----------------------------
 cloud-init.sh.tftpl        |  2 +-
 dynamic-pipeline.yml       | 70 ++++++++++++++++++++++++++++++++++++
 generate-child-pipeline.sh |  6 ++++
 main.tf                    | 13 +++----
 5 files changed, 100 insertions(+), 63 deletions(-)
 create mode 100644 dynamic-pipeline.yml
 create mode 100644 generate-child-pipeline.sh

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1affdae..2f6e6ac 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,63 +1,23 @@
-variables:
-  CI_TEMPLATE_REGISTRY_HOST: registry.gitlab.inria.fr
-  TF_STATE_NAME: default
+workflow:
+  rules:
+    - if: $CLOUDSTACK_API_KEY
 
-include:
-  - template: Terraform/Base.gitlab-ci.yml  # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Terraform/Base.gitlab-ci.yml
-
-stages:
-  - validate
-  - deploy
-  - execute
-  - cleanup
-
-fmt:
+generate-child-pipeline:
   tags:
     - linux
     - small
-  extends: .terraform:fmt
-
-validate:
-  tags:
-    - linux
-    - small
-  extends: .terraform:validate
-  before_script:
-    - cp $SSH_PRIVATE_KEY id_rsa
-
-deploy:
-  stage: deploy
-  tags:
-    - linux
-    - small
-  script:
-    - cp $SSH_PRIVATE_KEY id_rsa
-    - gitlab-terraform plan -var runner_count=0
-    - gitlab-terraform apply
-    - gitlab-terraform plan -var runner_count=3
-    - gitlab-terraform apply
-
-execute:
-  stage: execute
   image: alpine
-  parallel:
-    matrix:
-      - index: [0, 1, 2]
-  tags:
-    - terraform
-    - docker
-    - runner-$index
   script:
-    - echo Greetings from runner $index!
+    - sh generate-child-pipeline.sh > generated-child-pipeline.yml
+  artifacts:
+    paths:
+      - generated-child-pipeline.yml
 
-cleanup:
-  stage: cleanup
-  tags:
-    - linux
-    - small
-  script:
-    - cd "${TF_ROOT}"
-    - cp $SSH_PRIVATE_KEY id_rsa
-    - gitlab-terraform plan -var runner_count=0
-    - gitlab-terraform apply
-  when: always
+execute-child-pipeline:
+  needs:
+    - generate-child-pipeline
+  trigger:
+    include:
+      - artifact: generated-child-pipeline.yml
+        job: generate-child-pipeline
+  
diff --git a/cloud-init.sh.tftpl b/cloud-init.sh.tftpl
index 2b4d6de..4111b67 100644
--- a/cloud-init.sh.tftpl
+++ b/cloud-init.sh.tftpl
@@ -16,7 +16,7 @@
   # We install docker.io to be able to register a docker executor
   apt-get install --yes gitlab-runner docker.io
   gitlab-runner register --non-interactive \
-    --tag-list terraform,docker,runner-${index} \
+    --tag-list terraform,docker,pipeline-${CI_PARENT_PIPELINE_ID},runner-${index} \
     --executor docker --docker-image alpine --url https://gitlab.inria.fr \
     --registration-token ${REGISTRATION_TOKEN}
 ) >>/root/log.txt 2>&1
diff --git a/dynamic-pipeline.yml b/dynamic-pipeline.yml
new file mode 100644
index 0000000..6296b34
--- /dev/null
+++ b/dynamic-pipeline.yml
@@ -0,0 +1,70 @@
+# Workaround for child pipeline to work in merge requests
+# See: https://gitlab.com/gitlab-org/gitlab/-/issues/222370#note_662695503
+workflow:
+  rules:
+    - if: $CI_MERGE_REQUEST_IID
+    - if: $CI_COMMIT_BRANCH
+
+variables:
+  CI_TEMPLATE_REGISTRY_HOST: registry.gitlab.inria.fr
+  TF_STATE_NAME: pipeline-$CI_PARENT_PIPELINE_ID
+  TF_VAR_CI_PARENT_PIPELINE_ID: $CI_PARENT_PIPELINE_ID
+
+include:
+  - template: Terraform/Base.gitlab-ci.yml  # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Terraform/Base.gitlab-ci.yml
+
+stages:
+  - validate
+  - deploy
+  - execute
+  - cleanup
+
+fmt:
+  tags:
+    - linux
+    - small
+  extends: .terraform:fmt
+
+validate:
+  tags:
+    - linux
+    - small
+  extends: .terraform:validate
+  before_script:
+    - cp $SSH_PRIVATE_KEY id_rsa
+
+deploy:
+  stage: deploy
+  tags:
+    - linux
+    - small
+  script:
+    - cp $SSH_PRIVATE_KEY id_rsa
+    - gitlab-terraform plan
+    - gitlab-terraform apply
+
+execute:
+  stage: execute
+  image: alpine
+  parallel:
+    matrix:
+      - index: [0, 1, 2]
+  tags:
+    - terraform
+    - docker
+    - pipeline-$CI_PARENT_PIPELINE_ID
+    - runner-$index
+  script:
+    - echo Greetings from runner $index!
+
+cleanup:
+  stage: cleanup
+  tags:
+    - linux
+    - small
+  script:
+    - cd "${TF_ROOT}"
+    - cp $SSH_PRIVATE_KEY id_rsa
+    - gitlab-terraform destroy
+    - 'curl --user "gitlab-ci-token:$CI_JOB_TOKEN" --request DELETE "$CI_API_V4_URL/projects/$CI_PROJECT_ID/terraform/state/$TF_STATE_NAME"'
+  when: always
diff --git a/generate-child-pipeline.sh b/generate-child-pipeline.sh
new file mode 100644
index 0000000..b127e0f
--- /dev/null
+++ b/generate-child-pipeline.sh
@@ -0,0 +1,6 @@
+set -ex
+cat <<EOF
+include: "dynamic-pipeline.yml"
+variables:
+  CI_PARENT_PIPELINE_ID: "$CI_PIPELINE_ID"
+EOF
diff --git a/main.tf b/main.tf
index 7683dcf..86bb8b4 100644
--- a/main.tf
+++ b/main.tf
@@ -24,13 +24,13 @@ variable "SSH_PUBLIC_KEY" {
   type = string
 }
 
-variable "runner_count" {
+variable "CI_PARENT_PIPELINE_ID" {
   type = number
 }
 
 resource "cloudstack_instance" "runner" {
-  count            = var.runner_count
-  name             = "gitlabcigallery-terraform-runner-${count.index}"
+  count            = 3
+  name             = "gitlabcigallery-terraform-pipeline-${var.CI_PARENT_PIPELINE_ID}-${count.index}"
   service_offering = "Custom"
   template         = "ubuntu-20.04-lts"
   zone             = "zone-ci"
@@ -40,9 +40,10 @@ resource "cloudstack_instance" "runner" {
   }
   expunge = true
   user_data = templatefile("cloud-init.sh.tftpl", {
-    index              = count.index
-    REGISTRATION_TOKEN = var.REGISTRATION_TOKEN
-    SSH_PUBLIC_KEY     = var.SSH_PUBLIC_KEY
+    index                 = count.index
+    REGISTRATION_TOKEN    = var.REGISTRATION_TOKEN
+    SSH_PUBLIC_KEY        = var.SSH_PUBLIC_KEY
+    CI_PARENT_PIPELINE_ID = var.CI_PARENT_PIPELINE_ID
   })
   connection {
     type                = "ssh"
-- 
GitLab