diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f12a535452b3040f0bd8c0efc2ccf8dc5313c758..800b49474c02df4399206e97b9b9cc935b5f717b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -16,8 +16,9 @@ stages: - execute - cleanup -before_script: - - cp $SSH_PRIVATE_KEY id_rsa +.with-rsa-key: + before_script: + - cp $SSH_PRIVATE_KEY id_rsa fmt: tags: @@ -29,13 +30,17 @@ validate: tags: - linux - small - extends: .terraform:validate + extends: + - .terraform:validate + - .with-rsa-key build: tags: - linux - small - extends: .terraform:build + extends: + - .terraform:build + - .with-rsa-key environment: name: $TF_STATE_NAME @@ -43,23 +48,64 @@ deploy: tags: - linux - small - extends: .terraform:deploy + extends: + - .terraform:deploy + - .with-rsa-key dependencies: - build rules: - - when: manual -execute: +execute linux: stage: execute image: alpine tags: - terraform - docker script: - - echo Greetings from runner! + - apk add gcc musl-dev + - gcc -o hello_world.linux hello_world.c + artifacts: + paths: + - hello_world.linux + +execute windows: + stage: execute + image: alpine + tags: + - terraform + - windows + script: + # The trick for running cmd scripts from powershell runner is documented + # here: + # https://gitlab.com/guided-explorations/microsoft/windows/call-cmd-from-powershell/-/blob/master/.gitlab-ci.yml + - | + set-content $env:public\inline.cmd -Value @' + call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" + cl /Fe:hello_world.exe hello_world.c + '@ + CMD.EXE /C $env:public\inline.cmd + exit $LASTEXITCODE + artifacts: + paths: + - hello_world.exe + +execute macos: + stage: execute + image: alpine + tags: + - terraform + - macos + script: + - clang -o hello_world.macos hello_world.c + artifacts: + paths: + - hello_world.macos destroy: tags: - linux - small - extends: .terraform:destroy + extends: + - .terraform:destroy + - .with-rsa-key + diff --git a/CHANGES.md b/CHANGES.md index 9593c7a4517ba08f3a5d16231fb4922f8f8a0bd2..de05d2f7afb269c03be5b75b076e6e4f5e4607c9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +# 2023-02-28 + +- !4 Add Windows and Mac OS X runners + # 2023-02-22 - !3 Use cloud-init configuration file instead of shell script diff --git a/README.md b/README.md index 775745a84f1ed1791bdd47c23aa544c05b8c2946..a05fdd96fc8a777c93cc55f75d9c840bf0449b9d 100644 --- a/README.md +++ b/README.md @@ -174,20 +174,30 @@ The value of the `SSH_PUBLIC_KEY` variable will be stored in the file connect to the virtual machines with the private key to unregister the runners before destroying the machines. -In this example, we set up only one resource: +In this example, we set up three resources: +a virtual machine running on Ubuntu 20.04, +a virtual machine running on Windows 10, and +a virtual machine running on Mac OS X 15. +The three virtual machines register themselves as runners on gitlab.inria.fr: +the Ubuntu machine provides a `docker` executor, +Windows and Mac OS X provide `shell` executors +(`powershell` for Windows, `bash` for Mac OS X). + +### Ubuntu 20.04 virtual machine + ```terraform -resource "cloudstack_instance" "custom_instance" { +resource "cloudstack_instance" "ubuntu" { ## It is a good practice to have the "{project name}-" prefix ## in VM names. - name = "gitlabcigallery-terraform-custom-instance" + name = "gitlabcigallery-terraform-ubuntu" service_offering = "Custom" template = "ubuntu-20.04-lts" zone = "zone-ci" details = { - cpuNumber = 4 + cpuNumber = 2 memory = 2048 } - expunge = true + expunge = true user_data = templatefile("cloud-init.yaml.tftpl", { REGISTRATION_TOKEN = var.REGISTRATION_TOKEN SSH_PUBLIC_KEY = var.SSH_PUBLIC_KEY @@ -210,7 +220,7 @@ resource "cloudstack_instance" "custom_instance" { - `custom_instance` is an identifier for the resource, which can be used to refer to it elsewhere in the Terraform configuration; -- `gitlabcigallery-terraform-custom-instance` is the name of the +- `gitlabcigallery-terraform-ubuntu` is the name of the virtual machine: by convention, the prefix `gitlabcigallery-terraform` is the name of the project on ci.inria.fr. - The service offering `Custom` allows us to specify the characterics @@ -221,10 +231,13 @@ resource "cloudstack_instance" "custom_instance" { The available templates can be listed with the ci.inria.fr portal in the virtual machine creation form ([portal documentation](https://ci.inria.fr/doc/page/web_portal_tutorial/#slaves)). - The template should be configured for [`cloud-init`](https://cloud-init.io/) - to take into account the CloudStack user-data + We rely here on the fact that [`cloud-init`](https://cloud-init.io/) + is installed in the template and takes into account the CloudStack user-data ([CloudStack documentation for cloud-init support](https://docs.cloudstack.apache.org/projects/cloudstack-administration/en/latest/virtual_machines.html#user-data-and-meta-data)). - Templates will be progressively updated to be configured as such by default. + We could also use a `remote-exec` provisioner to connect the virtual machine + via SSH to execute an initialization script on first boot: we will use + this method with Windows and Mac OS X virtual machines, since `cloud-init` + only exists on Linux. - There is only one zone, `zone-ci`, and `expunge` should be set to `true` to ask CloudStack to destroy the virtual machine immediately when Terraform needs to replace it (by default, virtual machines are @@ -251,6 +264,105 @@ resource "cloudstack_instance" "custom_instance" { in case of the `gitlab-runner` command was not yet installed when destroying occurs. +### Windows 10 virtual machine + +```terraform +resource "cloudstack_instance" "windows" { + ## It is a good practice to have the "{project name}-" prefix + ## in VM names. + name = "gitlabcigallery-terraform-windows" + service_offering = "Custom" + template = "windows10-vs2022-runner" + zone = "zone-ci" + details = { + cpuNumber = 2 + memory = 2048 + } + expunge = true + connection { + type = "ssh" + host = self.name + user = "ci" + password = "ci" + bastion_host = "ci-ssh.inria.fr" + bastion_user = "gter001" + bastion_private_key = file("id_rsa") + target_platform = "windows" + } + provisioner "remote-exec" { + inline = [<<-EOF + gitlab-runner start + gitlab-runner register --non-interactive --tag-list terraform,windows --executor shell --shell powershell --url https://gitlab.inria.fr --registration-token ${var.REGISTRATION_TOKEN} + EOF + ] + } + provisioner "remote-exec" { + when = destroy + inline = ["gitlab-runner unregister --all-runners || true"] + } +} +``` + +- It is essential to specify `target_platform = "windows"` for the SSH + connection to work. + +- `gitlab-runner` service is not started on boot by default in the + template: we start the service explicitely before registering the runner. + +### Mac OS X 15 virtual machine + +```terraform +resource "cloudstack_instance" "macos" { + ## It is a good practice to have the "{project name}-" prefix + ## in VM names. + name = "gitlabcigallery-terraform-macos" + service_offering = "Custom" + template = "osx-15-runner" + zone = "zone-ci" + details = { + cpuNumber = 2 + memory = 2048 + } + expunge = true + connection { + type = "ssh" + host = self.name + user = "ci" + password = "ci" + bastion_host = "ci-ssh.inria.fr" + bastion_user = "gter001" + bastion_private_key = file("id_rsa") + } + provisioner "remote-exec" { + inline = [<<-EOF + set -ex + ( + export PATH=/usr/local/bin:$PATH + gitlab-runner register --non-interactive --tag-list terraform,macos --executor shell --url https://gitlab.inria.fr --registration-token ${var.REGISTRATION_TOKEN} + ) >~/log.txt 2>&1 + EOF + ] + } + provisioner "remote-exec" { + when = destroy + inline = [<<-EOF + export PATH=/usr/local/bin:$PATH + gitlab-runner unregister --all-runners || true" + EOF + ] + } +} +``` + +- In the `remote-exec` provisioner, outputs are redirected to `~/log.txt` to ease + debugging, since they are not shown in GitLab log. + +- The executable `gitlab-runner` is in `/usr/local/bin`, which is added to `PATH` + by `.bashrc` (or `.zshrc`), which is not sourced when commands are executed via SSH + non interactively. + Therefore, we add `/usr/local/bin` specifically to `PATH` before executing + `gitlab-runner` (we could have sourced `. ~/.bashrc` instead). + ## The cloud-init configuration file [`cloud-init.yaml.tftpl`](cloud-init.yaml.tftpl) The cloud-init configuration file @@ -286,13 +398,14 @@ The container is available `registry.gitlab.inria.fr` (that we use for `CI_TEMPLATE_REGISTRY_HOST`), in the project `gitlab-org/terraform-images`. -There are four stages: +There are five stages: ```yaml stages: - validate - build - deploy - execute + - destroy ``` - In the stage `validate`, the step `validate` checks that there is no @@ -315,6 +428,12 @@ stages: the same project, but we could have chosen to register the runner to another project by adjusting the `REGISTRATION_TOKEN` variable.) +- In the stage `destroy`, the homonymous step is to be run manually and + executes `gitlab-terraform destroy`, + which destroys the virtual machines (and these virtual machines have + `remote-exec` destroy provisioners that unregister themselves from + gitlab.inria.fr). + Every job that will use the Terraform configuration file needs to copy the file referred by `SSH_PRIVATE_KEY` into the file `id_rsa`. To copy the file without overriding all the script, diff --git a/hello_world.c b/hello_world.c new file mode 100644 index 0000000000000000000000000000000000000000..d2f0b5d36412e031576ba01393ce554ca47fe9b3 --- /dev/null +++ b/hello_world.c @@ -0,0 +1,8 @@ +#include <stdio.h> + +int +main(int argc, char *argv[]) +{ + printf("Hello, world!"); + return 0; +} diff --git a/main.tf b/main.tf index ff8aecfba8a1918702c614aa51bda89845d39c1d..93085fb2cf6830f25f1608db0cc4032c79f4ad3a 100644 --- a/main.tf +++ b/main.tf @@ -13,26 +13,27 @@ provider "cloudstack" { ## Provided by environment (Gitlab secrets) # api_key = "${var.cloudstack_api_key}" # secret_key = "${var.cloudstack_secret_key}" + + timeout = 1200 } variable "REGISTRATION_TOKEN" { - type = string - sensitive = true + type = string } variable "SSH_PUBLIC_KEY" { type = string } -resource "cloudstack_instance" "custom_instance" { +resource "cloudstack_instance" "ubuntu" { ## It is a good practice to have the "{project name}-" prefix ## in VM names. - name = "gitlabcigallery-terraform-custom-instance" + name = "gitlabcigallery-terraform-ubuntu" service_offering = "Custom" template = "ubuntu-20.04-lts" zone = "zone-ci" details = { - cpuNumber = 4 + cpuNumber = 2 memory = 2048 } expunge = true @@ -54,3 +55,79 @@ resource "cloudstack_instance" "custom_instance" { inline = ["sudo gitlab-runner unregister --all-runners || true"] } } + +resource "cloudstack_instance" "windows" { + ## It is a good practice to have the "{project name}-" prefix + ## in VM names. + name = "gitlabcigallery-terraform-windows" + service_offering = "Custom" + template = "windows10-vs2022-runner" + zone = "zone-ci" + details = { + cpuNumber = 2 + memory = 2048 + } + expunge = true + connection { + type = "ssh" + host = self.name + user = "ci" + password = "ci" + bastion_host = "ci-ssh.inria.fr" + bastion_user = "gter001" + bastion_private_key = file("id_rsa") + target_platform = "windows" + } + provisioner "remote-exec" { + inline = [<<-EOF + gitlab-runner start + gitlab-runner register --non-interactive --tag-list terraform,windows --executor shell --shell powershell --url https://gitlab.inria.fr --registration-token ${var.REGISTRATION_TOKEN} + EOF + ] + } + provisioner "remote-exec" { + when = destroy + inline = ["gitlab-runner unregister --all-runners || true"] + } +} + +resource "cloudstack_instance" "macos" { + ## It is a good practice to have the "{project name}-" prefix + ## in VM names. + name = "gitlabcigallery-terraform-macos" + service_offering = "Custom" + template = "osx-15-runner" + zone = "zone-ci" + details = { + cpuNumber = 2 + memory = 2048 + } + expunge = true + connection { + type = "ssh" + host = self.name + user = "ci" + password = "ci" + bastion_host = "ci-ssh.inria.fr" + bastion_user = "gter001" + bastion_private_key = file("id_rsa") + } + provisioner "remote-exec" { + inline = [<<-EOF + set -ex + ( + export PATH=/usr/local/bin:$PATH + gitlab-runner register --non-interactive --tag-list terraform,macos --executor shell --url https://gitlab.inria.fr --registration-token ${var.REGISTRATION_TOKEN} + ) >~/log.txt 2>&1 + EOF + ] + } + provisioner "remote-exec" { + when = destroy + inline = [<<-EOF + export PATH=/usr/local/bin:$PATH + gitlab-runner unregister --all-runners || true" + EOF + ] + } +}