From c1ce4d2942ff4a733df4734d7ff9163890755611 Mon Sep 17 00:00:00 2001
From: kaiyou <dev@kaiyou.fr>
Date: Fri, 1 Dec 2023 15:01:06 +0100
Subject: [PATCH] Prepare for more versatile CI jobs

---
 ansible/bootstrap.yaml                        |  3 +-
 ansible/ciupload.yaml                         | 42 +++++++++++++++++++
 ansible/cleanup.yaml                          |  7 ++++
 ansible/cloud.yaml                            |  4 +-
 ansible/deploy.yaml                           | 13 +++++-
 .../roles/cloud/tasks/cleanup_scaleway.yaml   | 33 ++++++++-------
 ansible/roles/cloud/tasks/hcloud.yaml         |  5 +++
 ansible/roles/cloud/tasks/scaleway.yaml       | 37 ++++++++--------
 ansible/roles/hepto/tasks/main.yaml           |  2 +-
 ansible/test_podinfo.yaml                     | 18 ++++----
 10 files changed, 115 insertions(+), 49 deletions(-)
 create mode 100644 ansible/ciupload.yaml
 create mode 100644 ansible/cleanup.yaml

diff --git a/ansible/bootstrap.yaml b/ansible/bootstrap.yaml
index d9a805c..92dcb5b 100644
--- a/ansible/bootstrap.yaml
+++ b/ansible/bootstrap.yaml
@@ -10,7 +10,7 @@
     environment:
       KUBECONFIG: "{{ kubeconfig }}"
     retries: 10
-    delay: 15
+    delay: 5
 
   - name: Dump cluster info
     ansible.builtin.shell: |
@@ -27,3 +27,4 @@
       helm upgrade --install hepto /tmp/hepto-bootstrap -f /tmp/cluster_info
     environment:
       KUBECONFIG: "{{ kubeconfig }}"
+
diff --git a/ansible/ciupload.yaml b/ansible/ciupload.yaml
new file mode 100644
index 0000000..9df744b
--- /dev/null
+++ b/ansible/ciupload.yaml
@@ -0,0 +1,42 @@
+---
+# These tasks uploads the build artifact from CI to some S#
+# bucket, for two main reasons:
+# - it is complex and unsupported to download the artifact of a current build directly from Gitlab
+# - uploading from the CI to many cloud providers can become costly
+
+- name: Try and get the url
+  amazon.aws.s3_object:
+    endpoint_url: "{{ s3_endpoint }}"
+    bucket: "{{ s3_bucket }}"
+    region: "{{ s3_region }}"
+    access_key: "{{ s3_access_key }}"
+    secret_key: "{{ s3_secret_key }}"
+    object: "hepto.{{ lookup('env', 'CI_PIPELINE_ID') }}"
+    mode: geturl
+  register: get
+  ignore_errors: true
+
+- name: Upload the file when necessary
+  when: get.failed
+  amazon.aws.s3_object:
+    endpoint_url: "{{ s3_endpoint }}"
+    bucket: "{{ s3_bucket }}"
+    region: "{{ s3_region }}"
+    access_key: "{{ s3_access_key }}"
+    secret_key: "{{ s3_secret_key }}"
+    object: "hepto.{{ lookup('env', 'CI_PIPELINE_ID') }}"
+    src: "{{ lookup('env', 'PWD') }}/hepto"
+    mode: put
+    encrypt: false
+  register: put
+
+# This is hacky as hell, yet required for the fact to be properly altered in
+# all hosts. The when clause makes it possible to call this outsite the playbook
+# during CI warmup
+- name: Set the hepto download url for nodes
+  delegate_to: "{{ item }}"
+  delegate_facts: true
+  when: "'nodes' in groups"
+  with_items: "{{ groups['nodes'] }}"
+  set_fact:
+    hepto_url: "{{ put.url if put.changed else get.url }}"
diff --git a/ansible/cleanup.yaml b/ansible/cleanup.yaml
new file mode 100644
index 0000000..7160246
--- /dev/null
+++ b/ansible/cleanup.yaml
@@ -0,0 +1,7 @@
+---
+- hosts: localhost
+  tasks:
+  - name: Cleanup cloud deployment
+    include_role:
+      name: cloud
+      tasks_from: cleanup.yaml
diff --git a/ansible/cloud.yaml b/ansible/cloud.yaml
index 178c945..01a7666 100644
--- a/ansible/cloud.yaml
+++ b/ansible/cloud.yaml
@@ -1,6 +1,6 @@
 ---
+# This play merely creates nodes and/or probes them, for inclusion
+# when deploying or using the cluster
 - hosts: localhost
   roles:
   - cloud
-
-- import_playbook: deploy.yaml
diff --git a/ansible/deploy.yaml b/ansible/deploy.yaml
index 6fe2efd..1f84669 100644
--- a/ansible/deploy.yaml
+++ b/ansible/deploy.yaml
@@ -1,6 +1,17 @@
 ---
+# This play will do nothing if no cloud deploying is specified
+- import_playbook: cloud.yaml
+
+# If this is a CI deployment, upload hepto to a cloud URL
+- hosts: localhost
+  tasks:
+    - when: "lookup('env', 'CI_PIPELINE_ID') != ''"
+      include_tasks: ./ciupload.yaml
+
+# Deploy the nodes, either epxlicitely declared or deployed to cloud
 - hosts: nodes
   roles:
   - hepto
 
-- ansible.builtin.import_playbook: bootstrap.yaml
+# Bootstrap the cluster
+- import_playbook: bootstrap.yaml
diff --git a/ansible/roles/cloud/tasks/cleanup_scaleway.yaml b/ansible/roles/cloud/tasks/cleanup_scaleway.yaml
index 17114c1..ea5ced3 100644
--- a/ansible/roles/cloud/tasks/cleanup_scaleway.yaml
+++ b/ansible/roles/cloud/tasks/cleanup_scaleway.yaml
@@ -1,16 +1,3 @@
-- name: "Get servers information"
-  community.general.scaleway_server_info:
-    api_token: "{{ scaleway_token }}"
-    region: "{{ scaleway_region }}"
-  register: raw_servers
-
-- name: "Index servers information"
-  ansible.builtin.set_fact:
-    servers: "{{ raw_servers['scaleway_server_info'] 
-               | map(attribute='name')
-               | zip(raw_servers['scaleway_server_info'])
-               | community.general.dict }}"
-
 - name: "Delete nodes"
   community.general.scaleway_compute:
     api_token: "{{ scaleway_token }}"
@@ -20,14 +7,28 @@
     project: "{{ scaleway_project }}"
     region: "{{ scaleway_region }}"
     state: absent
-  register: deleted
+  register: servers
   with_dict: "{{ nodes }}"
 
+- name: Debug
+  debug:
+    msg: "{{ servers }}"
+
+# Scaleway module does not offer to delete volumes when deleting nodes,
+# so we loop and try to delete all unattached volumes
+
+- name: "Get volume infos"
+  community.general.scaleway_volume_info:
+    api_token: "{{ scaleway_token }}"
+    region: "{{ scaleway_region }}"
+  register: volumes
+
 - name: "Delete volumes"
   community.general.scaleway_volume:
     api_token: "{{ scaleway_token }}"
-    name: "{{ servers[item.invocation.module_args.name].volumes[0].name }}"
+    name: "{{ item.name }}"
     project: "{{ scaleway_project }}"
     region: "{{ scaleway_region }}"
     state: absent
-  with_dict: "{{ deleted.results }}"
+  with_items: "{{ volumes.scaleway_volume_info }}"
+  when: item.state == "available"
diff --git a/ansible/roles/cloud/tasks/hcloud.yaml b/ansible/roles/cloud/tasks/hcloud.yaml
index 0cdddbf..5736f3e 100644
--- a/ansible/roles/cloud/tasks/hcloud.yaml
+++ b/ansible/roles/cloud/tasks/hcloud.yaml
@@ -24,10 +24,15 @@
   add_host:
     name: "{{ item.item.key }}"
     groups: "{{ ['nodes'] + (nodes[item.item.key]|d([])) }}"
+    # Hcloud does not return the node ip, it is always ::1 in the prefix however
     ansible_host: "{{ item.hcloud_server.ipv6 | ansible.utils.ipaddr('net') | ansible.utils.ipaddr('1') | ansible.utils.ipaddr('address') }}"
     ansible_user: root
+    # Hcloud provides a generic ip for the v6 gateway
     node_gw: "fe80::1"
+    # Use the ::2 address inside allocated prefix for hepto
     node_ip: "{{ item.hcloud_server.ipv6 | ansible.utils.ipaddr('net') | ansible.utils.ipaddr('2') }}"
+    # Hcloud overrides the default interface to eth0 on every OS
+    node_iface: eth0
     ansible_ssh_extra_args: "-o StrictHostKeyChecking=no"
   with_items: "{{ servers.results }}"
 
diff --git a/ansible/roles/cloud/tasks/scaleway.yaml b/ansible/roles/cloud/tasks/scaleway.yaml
index 2c5c507..a4408cc 100644
--- a/ansible/roles/cloud/tasks/scaleway.yaml
+++ b/ansible/roles/cloud/tasks/scaleway.yaml
@@ -8,37 +8,40 @@
     region: "{{ scaleway_region }}"
     enable_ipv6: true
     state: running
-  register: created
   with_dict: "{{ nodes }}"
 
-- name: "Get servers information"
-  community.general.scaleway_server_info:
+# We start nodes again so we get their IP address, which is
+# not yet available at creation time
+- name: "Probe nodes"
+  community.general.scaleway_compute:
     api_token: "{{ scaleway_token }}"
+    name: "{{ node_prefix }}-{{ item.key }}"
+    commercial_type: "{{ scaleway_type }}"
+    image: "{{ scaleway_image }}"
+    project: "{{ scaleway_project }}"
     region: "{{ scaleway_region }}"
-  register: raw_servers
-
-- name: "Index servers information"
-  ansible.builtin.set_fact:
-    servers: "{{ raw_servers['scaleway_server_info'] 
-               | map(attribute='name')
-               | zip(raw_servers['scaleway_server_info'])
-               | community.general.dict }}"
+    state: running
+  register: servers
+  with_dict: "{{ nodes }}"
 
 - name: Wait for nodes to be ready
   ansible.builtin.wait_for:
     port: 22
-    host: "{{ servers[item.invocation.module_args.name].ipv6.address }}"
+    host: "{{ item.msg.ipv6.address }}"
     delay: 2
-  with_items: "{{ created.results }}"
+  with_items: "{{ servers.results }}"
 
 - name: "Add nodes to inventory"
   add_host:
     name: "{{ item.item.key }}"
     groups: "{{ ['nodes'] + (nodes[item.item.key]|d([])) }}"
-    ansible_host: "{{ servers[item.invocation.module_args.name].ipv6.address }}"
+    ansible_host: "{{ item.msg.ipv6.address }}"
     ansible_user: root
-    node_gw: "{{ servers[item.invocation.module_args.name].ipv6.gateway }}"
-    node_ip: "{{ servers[item.invocation.module_args.name].ipv6.address | ansible.utils.ipmath(1) }}/{{ servers[item.invocation.module_args.name].ipv6.netmask }}"
+    node_gw: "{{ item.msg.ipv6.gateway }}"
+    # We use the next (usually ::2) available ip for hepto
+    node_ip: "{{ item.msg.ipv6.address | ansible.utils.ipmath(1) }}/{{ item.msg.ipv6.netmask }}"
+    # This is specific to scaleway
+    node_iface: ens2
     ansible_ssh_extra_args: "-o StrictHostKeyChecking=no"
-  with_items: "{{ created.results }}"
+  with_items: "{{ servers.results }}"
 
diff --git a/ansible/roles/hepto/tasks/main.yaml b/ansible/roles/hepto/tasks/main.yaml
index c2e027d..1e3e091 100644
--- a/ansible/roles/hepto/tasks/main.yaml
+++ b/ansible/roles/hepto/tasks/main.yaml
@@ -11,7 +11,7 @@
   stat:
     path: "{{ hepto_bin }}"
   register: hepto_exists
-  
+
 - name: Download hepto binary for amd64
   get_url:
     url: "{{ hepto_url }}"
diff --git a/ansible/test_podinfo.yaml b/ansible/test_podinfo.yaml
index 9561408..5b4fb47 100644
--- a/ansible/test_podinfo.yaml
+++ b/ansible/test_podinfo.yaml
@@ -1,13 +1,15 @@
 ---
-#- ansible.builtin.import_playbook: cloud.yaml
+# Import the cloud playbook to populate inventory
+- import_playbook: cloud.yaml
 
-- hosts: mastere
-  tasks:
-    
+# Do the actual testing from master
+- hosts: master
+  tasks:    
   - name: Deploy podinfo
     ansible.builtin.shell: |
       helm repo add --force-update podinfo https://stefanprodan.github.io/podinfo
       helm repo update
+      # Podinfo default repository does not expose ipv6, switch to docker.io
       helm upgrade --install podinfo podinfo/podinfo --set image.repository=docker.io/stefanprodan/podinfo
     environment:
       KUBECONFIG: "{{ kubeconfig }}"
@@ -18,16 +20,10 @@
     environment:
       KUBECONFIG: "{{ kubeconfig }}"
 
+  # This is run from master for now, running from localhost is too complex
   - name: Try and access the public URL
     ansible.builtin.get_url:
       url: "http://[{{ external_ips | first }}]:9898"
       dest: /tmp
     retries: 100
     delay: 30
-
-- hosts: localhost
-  tasks:
-  - name: Cleanup cloud deployment
-    include_role:
-      name: cloud
-      tasks_from: cleanup.yaml
-- 
GitLab