<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>GitHub Actions on Valérian Pyckaert</title>
    <link>https://valerian-pyckaert.dev/tags/github-actions/</link>
    <description>Recent content in GitHub Actions on Valérian Pyckaert</description>
    <image>
      <title>Valérian Pyckaert</title>
      <url>https://valerian-pyckaert.dev/images/default-cover.svg</url>
      <link>https://valerian-pyckaert.dev/images/default-cover.svg</link>
    </image>
    <generator>Hugo</generator>
    <language>en</language>
    <lastBuildDate>Sun, 28 Jun 2026 16:45:55 +0200</lastBuildDate>
    <atom:link href="https://valerian-pyckaert.dev/tags/github-actions/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>CI/CD Terraform complète sur GCP en 3 étapes</title>
      <link>https://valerian-pyckaert.dev/posts/the-forge-cicd/</link>
      <pubDate>Sun, 28 Jun 2026 16:45:55 +0200</pubDate>
      <guid>https://valerian-pyckaert.dev/posts/the-forge-cicd/</guid>
      <description>De terraform apply local à une CI/CD complète sur GCP en 3 étapes.</description>
      <content:encoded><![CDATA[<p>Dans les articles précédents de la série <code>TheForge</code>, nous avons vu comment déployer un cluster GKE + VPC + ArgoCD sur GCP via Terraform.</p>
<p>Cette stack est fonctionnelle mais elle a ses limites : le state Terraform est local, l&rsquo;authentification GCP repose sur <code>gcloud</code> et il n&rsquo;y a aucun workflow CI/CD.</p>
<p>Comment passer d&rsquo;un workflow PoC local à une configuration CI/CD complète applicable en production ?</p>
<ul>
<li>Authentification GCP via Workload Identity Federation (WIF) pour GitHub Actions</li>
<li>Remote state Terraform sur GCS</li>
<li>Workflows GitHub Actions</li>
</ul>
<h2 id="workload-identity-federation">Workload Identity Federation</h2>
<p><a href="https://docs.cloud.google.com/iam/docs/workload-identity-federation?hl=fr">Workload Identity Federation (WIF)</a> est une fonctionnalité de GCP qui permet à des applications externes (comme GitHub Actions) d&rsquo;obtenir des identités temporaires pour accéder aux ressources GCP, sans avoir besoin de stocker des clés JSON de service account.</p>
<h3 id="variables-shell-à-exporter">Variables shell à exporter</h3>
<p><em>Remplacez les valeurs par celles de votre projet GCP et de votre repo GitHub.</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">PROJECT_ID</span><span class="o">=</span><span class="s2">&#34;deft-accord-496812-k9&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">REGION</span><span class="o">=</span><span class="s2">&#34;europe-west1&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">GITHUB_ORG</span><span class="o">=</span><span class="s2">&#34;Bernedotcom2312&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">GITHUB_REPO</span><span class="o">=</span><span class="s2">&#34;theforge-infra&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">SA_NAME</span><span class="o">=</span><span class="s2">&#34;github-actions-terraform&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">SA_EMAIL</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">SA_NAME</span><span class="si">}</span><span class="s2">@</span><span class="si">${</span><span class="nv">PROJECT_ID</span><span class="si">}</span><span class="s2">.iam.gserviceaccount.com&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">WIF_POOL</span><span class="o">=</span><span class="s2">&#34;github-wif-pool&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">WIF_PROVIDER</span><span class="o">=</span><span class="s2">&#34;github-actions-provider&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">TF_STATE_BUCKET</span><span class="o">=</span><span class="s2">&#34;theforge-infra-tfstate-</span><span class="si">${</span><span class="nv">PROJECT_ID</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span></code></pre></div><h3 id="activer-les-apis-gcp-nécessaires">Activer les APIs GCP nécessaires</h3>
<p>Dans la suite de cet article nous allons avoir besoin d&rsquo;activer plusieurs APIs GCP pour que Terraform et GitHub Actions puissent interagir avec GCP, notamment l&rsquo;IAM, GKE, Compute Engine et Cloud Storage.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gcloud services <span class="nb">enable</span> cloudresourcemanager.googleapis.com iam.googleapis.com <span class="se">\
</span></span></span><span class="line"><span class="cl">  iamcredentials.googleapis.com sts.googleapis.com container.googleapis.com <span class="se">\
</span></span></span><span class="line"><span class="cl">  compute.googleapis.com storage.googleapis.com --project<span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">PROJECT_ID</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span></code></pre></div><h3 id="créer-le-bucket-gcs-pour-le-state-terraform">Créer le bucket GCS pour le state Terraform</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gcloud storage buckets create <span class="s2">&#34;gs://</span><span class="si">${</span><span class="nv">TF_STATE_BUCKET</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --project<span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">PROJECT_ID</span><span class="si">}</span><span class="s2">&#34;</span> --location<span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">REGION</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --uniform-bucket-level-access --public-access-prevention
</span></span><span class="line"><span class="cl">gcloud storage buckets update <span class="s2">&#34;gs://</span><span class="si">${</span><span class="nv">TF_STATE_BUCKET</span><span class="si">}</span><span class="s2">&#34;</span> --versioning
</span></span></code></pre></div><h3 id="créer-le-service-account">Créer le Service Account</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gcloud iam service-accounts create <span class="s2">&#34;</span><span class="si">${</span><span class="nv">SA_NAME</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --project<span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">PROJECT_ID</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --display-name<span class="o">=</span><span class="s2">&#34;GitHub Actions Terraform SA&#34;</span>
</span></span></code></pre></div><h3 id="attribuer-les-rôles-iam-minimaux">Attribuer les rôles IAM minimaux</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gcloud projects add-iam-policy-binding <span class="s2">&#34;</span><span class="si">${</span><span class="nv">PROJECT_ID</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --member<span class="o">=</span><span class="s2">&#34;serviceAccount:</span><span class="si">${</span><span class="nv">SA_EMAIL</span><span class="si">}</span><span class="s2">&#34;</span> --role<span class="o">=</span><span class="s2">&#34;roles/container.admin&#34;</span>
</span></span><span class="line"><span class="cl">gcloud projects add-iam-policy-binding <span class="s2">&#34;</span><span class="si">${</span><span class="nv">PROJECT_ID</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --member<span class="o">=</span><span class="s2">&#34;serviceAccount:</span><span class="si">${</span><span class="nv">SA_EMAIL</span><span class="si">}</span><span class="s2">&#34;</span> --role<span class="o">=</span><span class="s2">&#34;roles/compute.networkAdmin&#34;</span>
</span></span><span class="line"><span class="cl">gcloud projects add-iam-policy-binding <span class="s2">&#34;</span><span class="si">${</span><span class="nv">PROJECT_ID</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --member<span class="o">=</span><span class="s2">&#34;serviceAccount:</span><span class="si">${</span><span class="nv">SA_EMAIL</span><span class="si">}</span><span class="s2">&#34;</span> --role<span class="o">=</span><span class="s2">&#34;roles/viewer&#34;</span>
</span></span><span class="line"><span class="cl">gcloud storage buckets add-iam-policy-binding <span class="s2">&#34;gs://</span><span class="si">${</span><span class="nv">TF_STATE_BUCKET</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --member<span class="o">=</span><span class="s2">&#34;serviceAccount:</span><span class="si">${</span><span class="nv">SA_EMAIL</span><span class="si">}</span><span class="s2">&#34;</span> --role<span class="o">=</span><span class="s2">&#34;roles/storage.objectAdmin&#34;</span>
</span></span><span class="line"><span class="cl">gcloud iam service-accounts add-iam-policy-binding <span class="s2">&#34;</span><span class="si">${</span><span class="nv">SA_EMAIL</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --project<span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">PROJECT_ID</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --member<span class="o">=</span><span class="s2">&#34;serviceAccount:</span><span class="si">${</span><span class="nv">SA_EMAIL</span><span class="si">}</span><span class="s2">&#34;</span> --role<span class="o">=</span><span class="s2">&#34;roles/iam.serviceAccountUser&#34;</span>
</span></span></code></pre></div><h3 id="créer-le-pool-wif-et-le-provider-oidc">Créer le pool WIF et le provider OIDC</h3>
<p>GCP recommande de créer un pool WIF par environnement (dev, staging, prod) pour limiter l&rsquo;accès aux ressources GCP. Dans cet exemple, nous allons créer un pool WIF unique pour le repo GitHub.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gcloud iam workload-identity-pools create <span class="s2">&#34;</span><span class="si">${</span><span class="nv">WIF_POOL</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --project<span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">PROJECT_ID</span><span class="si">}</span><span class="s2">&#34;</span> --location<span class="o">=</span><span class="s2">&#34;global&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --display-name<span class="o">=</span><span class="s2">&#34;GitHub Actions WIF Pool&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">gcloud iam workload-identity-pools providers create-oidc <span class="s2">&#34;</span><span class="si">${</span><span class="nv">WIF_PROVIDER</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --project<span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">PROJECT_ID</span><span class="si">}</span><span class="s2">&#34;</span> --location<span class="o">=</span><span class="s2">&#34;global&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --workload-identity-pool<span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">WIF_POOL</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --issuer-uri<span class="o">=</span><span class="s2">&#34;https://token.actions.githubusercontent.com&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --attribute-mapping<span class="o">=</span><span class="s2">&#34;google.subject=assertion.sub,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --attribute-condition<span class="o">=</span><span class="s2">&#34;assertion.repository_owner == &#39;</span><span class="si">${</span><span class="nv">GITHUB_ORG</span><span class="si">}</span><span class="s2">&#39;&#34;</span>
</span></span></code></pre></div><p><em>L&rsquo;<code>attribute-condition</code> est ce qui garantit que seul mon organisation GitHub peut s&rsquo;authentifier, pas n&rsquo;importe qui avec un token OIDC GitHub.</em></p>
<h3 id="lier-le-sa-au-pool">Lier le SA au pool</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">WIF_POOL_NAME</span><span class="o">=</span><span class="k">$(</span>gcloud iam workload-identity-pools describe <span class="s2">&#34;</span><span class="si">${</span><span class="nv">WIF_POOL</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --project<span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">PROJECT_ID</span><span class="si">}</span><span class="s2">&#34;</span> --location<span class="o">=</span><span class="s2">&#34;global&#34;</span> --format<span class="o">=</span><span class="s2">&#34;value(name)&#34;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">gcloud iam service-accounts add-iam-policy-binding <span class="s2">&#34;</span><span class="si">${</span><span class="nv">SA_EMAIL</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --project<span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">PROJECT_ID</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --role<span class="o">=</span><span class="s2">&#34;roles/iam.workloadIdentityUser&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --member<span class="o">=</span><span class="s2">&#34;principalSet://iam.googleapis.com/</span><span class="si">${</span><span class="nv">WIF_POOL_NAME</span><span class="si">}</span><span class="s2">/attribute.repository/</span><span class="si">${</span><span class="nv">GITHUB_ORG</span><span class="si">}</span><span class="s2">/</span><span class="si">${</span><span class="nv">GITHUB_REPO</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span></code></pre></div><h3 id="récupérer-la-valeur-du-provider-wif-pour-github">Récupérer la valeur du provider WIF pour GitHub</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gcloud iam workload-identity-pools providers describe <span class="s2">&#34;</span><span class="si">${</span><span class="nv">WIF_PROVIDER</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --project<span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">PROJECT_ID</span><span class="si">}</span><span class="s2">&#34;</span> --location<span class="o">=</span><span class="s2">&#34;global&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --workload-identity-pool<span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">WIF_POOL</span><span class="si">}</span><span class="s2">&#34;</span> --format<span class="o">=</span><span class="s2">&#34;value(name)&#34;</span>
</span></span></code></pre></div><h3 id="configurer-les-variables-github">Configurer les variables GitHub</h3>
<table>
  <thead>
      <tr>
          <th>Variable</th>
          <th>Valeur</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>GCP_PROJECT_ID</code></td>
          <td><code>deft-accord-496812-k9</code></td>
      </tr>
      <tr>
          <td><code>GCP_REGION</code></td>
          <td><code>europe-west1</code></td>
      </tr>
      <tr>
          <td><code>GCP_SA_EMAIL</code></td>
          <td><code>github-actions-terraform@deft-accord-496812-k9.iam.gserviceaccount.com</code></td>
      </tr>
      <tr>
          <td><code>GCP_WIF_PROVIDER</code></td>
          <td>sortie de la commande 1.7</td>
      </tr>
      <tr>
          <td><code>TF_STATE_BUCKET</code></td>
          <td><code>theforge-infra-tfstate-deft-accord-496812-k9</code></td>
      </tr>
  </tbody>
</table>
<p><img alt="Terraform Github Actions Secrets" loading="lazy" src="/terraform-github-actions-secrets.png" title="Terraform GitHub Actions secrets"></p>
<h3 id="créer-lenvironnement-github-production">Créer l&rsquo;environnement GitHub <code>production</code></h3>
<p>Settings → Environments → New environment → <code>production</code></p>
<ul>
<li>Required reviewers : soi-même (ou le lead)</li>
<li>Deployment branches : <code>main</code> uniquement</li>
</ul>
<p><img alt="Terraform Github Actions Production Environment" loading="lazy" src="/terraform-prod-env.png" title="Terraform GitHub Actions production environment"></p>
<hr>
<h2 id="migration-du-state-vers-gcs">Migration du state vers GCS</h2>
<p>Maintenant que les pré-requis de gestion de droits sont en place, nous allons migrer le state Terraform local vers GCS.</p>
<h3 id="créer-backendtf">Créer <code>backend.tf</code></h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="cl"><span class="k">terraform</span> {
</span></span><span class="line"><span class="cl">  <span class="k">backend</span> <span class="s2">&#34;gcs&#34;</span> {
</span></span><span class="line"><span class="cl"><span class="n">    bucket</span> <span class="o">=</span> <span class="s2">&#34;lenomdevotrebucket&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">    prefix</span> <span class="o">=</span> <span class="s2">&#34;terraform/state&#34;</span>
</span></span><span class="line"><span class="cl">  }
</span></span><span class="line"><span class="cl">}
</span></span></code></pre></div><h3 id="migrer-le-state-local--gcs">Migrer le state local → GCS</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gcloud auth application-default login
</span></span><span class="line"><span class="cl">terraform init -migrate-state   <span class="c1"># répondre &#34;yes&#34; à la question de migration</span>
</span></span><span class="line"><span class="cl">terraform plan -var-file<span class="o">=</span>terraform.tfvars   <span class="c1"># vérifier que tout fonctionne, il ne doit pas y avoir de changement</span>
</span></span><span class="line"><span class="cl">rm terraform.tfstate terraform.tfstate.backup
</span></span><span class="line"><span class="cl">git add backend.tf
</span></span><span class="line"><span class="cl">git commit -m <span class="s2">&#34;chore: migrate Terraform state backend to GCS&#34;</span>
</span></span><span class="line"><span class="cl">git push origin main
</span></span></code></pre></div><hr>
<h2 id="workflows-github-actions">Workflows GitHub Actions</h2>
<p>Dernière étape : mettre en place les workflows GitHub Actions pour CI/CD pour valider notre configuration dans une PR et appliquer les changements sur <code>main</code> après approbation.</p>
<h3 id="fichiers-à-créer">Fichiers à créer</h3>
<pre tabindex="0"><code>.github/
└── workflows/
    ├── terraform-ci.yml   (PR → validate + plan commenté)
    └── terraform-cd.yml   (merge main → apply)
</code></pre><p><strong>terraform-ci.yml</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Terraform CI</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pull_request</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">branches</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;**.tf&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;**.tfvars&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;.github/workflows/terraform-ci.yml&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">permissions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">contents</span><span class="p">:</span><span class="w"> </span><span class="l">read</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">id-token</span><span class="p">:</span><span class="w"> </span><span class="l">write</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pull-requests</span><span class="p">:</span><span class="w"> </span><span class="l">write</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">terraform-ci</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Validate &amp; Plan</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-26.04</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">defaults</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">run</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">shell</span><span class="p">:</span><span class="w"> </span><span class="l">bash</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Setup</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">./.github/actions/terraform-setup</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">terraform_wrapper</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;true&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Terraform fmt</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">fmt</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">terraform fmt -check -recursive</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">continue-on-error</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Terraform init</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">init</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">          terraform init \
</span></span></span><span class="line"><span class="cl"><span class="sd">            -backend-config=&#34;bucket=${{ vars.TF_STATE_BUCKET }}&#34; \
</span></span></span><span class="line"><span class="cl"><span class="sd">            -input=false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Terraform validate</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">validate</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">terraform validate -no-color</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Setup TFLint</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">terraform-linters/setup-tflint@v6.2.2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">tflint_version</span><span class="p">:</span><span class="w"> </span><span class="l">v0.63.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">TFLint init</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">tflint --init</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">TFLint run</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">tflint</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">tflint --format=compact --no-color</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">continue-on-error</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Checkov scan</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">checkov</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">bridgecrewio/checkov-action@v12.1347.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">directory</span><span class="p">:</span><span class="w"> </span><span class="l">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">framework</span><span class="p">:</span><span class="w"> </span><span class="l">terraform</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">output_format</span><span class="p">:</span><span class="w"> </span><span class="l">cli</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">soft_fail</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">continue-on-error</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Terraform plan</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">plan</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">          terraform plan \
</span></span></span><span class="line"><span class="cl"><span class="sd">            -var=&#34;project_id=${{ vars.GCP_PROJECT_ID }}&#34; \
</span></span></span><span class="line"><span class="cl"><span class="sd">            -var=&#34;region=${{ vars.GCP_REGION }}&#34; \
</span></span></span><span class="line"><span class="cl"><span class="sd">            -out=plan.tfplan \
</span></span></span><span class="line"><span class="cl"><span class="sd">            -no-color \
</span></span></span><span class="line"><span class="cl"><span class="sd">            -input=false 2&gt;&amp;1 | tee plan-output.txt
</span></span></span><span class="line"><span class="cl"><span class="sd">          echo &#34;exitcode=${PIPESTATUS[0]}&#34; &gt;&gt; &#34;$GITHUB_OUTPUT&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">continue-on-error</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Upload plan artifact</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">if</span><span class="p">:</span><span class="w"> </span><span class="l">steps.plan.outcome == &#39;success&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/upload-artifact@v7.0.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">plan-${{ github.sha }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">plan.tfplan</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">retention-days</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span></code></pre></div><p><em>Si vous avez suivi mon article précédent sur les pre-commit pour terraform voici les contrôles que je vais appliquer : terraform fmt, terraform validate, tflint et checkov pour la sécurité.</em></p>
<p><strong>terraform-cd.yml</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Terraform CD</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">push</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">branches</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;**.tf&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;**.tfvars&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;.github/workflows/terraform-cd.yml&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">permissions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">contents</span><span class="p">:</span><span class="w"> </span><span class="l">read</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">id-token</span><span class="p">:</span><span class="w"> </span><span class="l">write</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">actions</span><span class="p">:</span><span class="w"> </span><span class="l">read</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">terraform-apply</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Apply to production</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-26.04</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w"> </span><span class="l">production</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">defaults</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">run</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">shell</span><span class="p">:</span><span class="w"> </span><span class="l">bash</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Setup</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">./.github/actions/terraform-setup</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Terraform init</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">          terraform init \
</span></span></span><span class="line"><span class="cl"><span class="sd">            -backend-config=&#34;bucket=${{ vars.TF_STATE_BUCKET }}&#34; \
</span></span></span><span class="line"><span class="cl"><span class="sd">            -input=false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Find CI run for this commit</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">find-ci-run</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/github-script@v7.0.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">github-token</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.GITHUB_TOKEN }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">script</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">            const { data } = await github.rest.actions.listWorkflowRuns({
</span></span></span><span class="line"><span class="cl"><span class="sd">              owner: context.repo.owner,
</span></span></span><span class="line"><span class="cl"><span class="sd">              repo: context.repo.repo,
</span></span></span><span class="line"><span class="cl"><span class="sd">              workflow_id: &#39;terraform-ci.yml&#39;,
</span></span></span><span class="line"><span class="cl"><span class="sd">              head_sha: context.sha,
</span></span></span><span class="line"><span class="cl"><span class="sd">              status: &#39;success&#39;,
</span></span></span><span class="line"><span class="cl"><span class="sd">              per_page: 1,
</span></span></span><span class="line"><span class="cl"><span class="sd">            });
</span></span></span><span class="line"><span class="cl"><span class="sd">            const run = data.workflow_runs[0];
</span></span></span><span class="line"><span class="cl"><span class="sd">            if (!run) core.setFailed(`No successful CI run found for ${context.sha}`);
</span></span></span><span class="line"><span class="cl"><span class="sd">            core.setOutput(&#39;run-id&#39;, String(run.id));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Download plan artifact</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/download-artifact@v8.0.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">plan-${{ github.sha }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">run-id</span><span class="p">:</span><span class="w"> </span><span class="l">${{ steps.find-ci-run.outputs.run-id }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">github-token</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.GITHUB_TOKEN }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Terraform apply</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">          terraform apply \
</span></span></span><span class="line"><span class="cl"><span class="sd">            -auto-approve \
</span></span></span><span class="line"><span class="cl"><span class="sd">            -input=false \
</span></span></span><span class="line"><span class="cl"><span class="sd">            plan.tfplan</span><span class="w">
</span></span></span></code></pre></div><p><em>Ce fichier sera déclenché uniquement sur le merge de la branche <code>main</code> et après approbation de l&rsquo;environnement <code>production</code>, c&rsquo;est le seul endroit où j&rsquo;autorise le <code>terraform apply</code> pour respecter les principes GitOps</em></p>
<hr>
<h2 id="vérification">Vérification</h2>
<p>Pour valider la chaine compplète il faut désormais appliquer un changement mineur (ex: ajout d&rsquo;un tag sur une ressource).</p>
<p>Vous devriez avoir un résultat semblable à celui-ci dans la PR :
<img alt="Terraform CI/CD Result" loading="lazy" src="/terraform-cicd-result-pr.png"></p>
<p>Une fois merged, vous devriez voir le workflow <code>terraform-cd.yml</code> s&rsquo;exécuter et appliquer les changements sur GCP :
<img alt="Terraform Pending Deployment" loading="lazy" src="/terraform-pending-deployment.png"></p>
<p>Il suffira ensuite d&rsquo;approuver le déploiement dans l&rsquo;environnement <code>production</code> pour que le <code>terraform apply</code> soit exécuté.</p>
<h2 id="apply-to-deployment"><img alt="Apply to deployment" loading="lazy" src="/terraform-cicd-apply.png"></h2>
<h2 id="conclusion">Conclusion</h2>
<p>Nous avons vu comment passer d&rsquo;un workflow Terraform local à une configuration CI/CD complète sur GCP applicable en entreprise en respectant les principes GitOps et en optimisant la boucle de feedback pour les développeurs.</p>
<p>L&rsquo;ensemble du code est disponible sur mon repo GitHub : <a href="https://github.com/Bernedotcom2312/theforge-infra">the-forge-infra</a>.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
