<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Patrick Domnick</title>
    <description>The latest articles on Forem by Patrick Domnick (@patrickdomnick).</description>
    <link>https://forem.com/patrickdomnick</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F565344%2Fb8a48ec6-ba32-49ae-80ae-ecdc5e53551c.png</url>
      <title>Forem: Patrick Domnick</title>
      <link>https://forem.com/patrickdomnick</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/patrickdomnick"/>
    <language>en</language>
    <item>
      <title>Packaging and Uploading a Docker Container and Helm Chart to GitLab using GitLab CI, Operator Framework, Kustomize, and Helmify</title>
      <dc:creator>Patrick Domnick</dc:creator>
      <pubDate>Sun, 25 Feb 2024 17:04:44 +0000</pubDate>
      <link>https://forem.com/patrickdomnick/packaging-and-uploading-a-docker-container-and-helm-chart-to-gitlab-using-gitlab-ci-operator-framework-kustomize-and-helmify-3j68</link>
      <guid>https://forem.com/patrickdomnick/packaging-and-uploading-a-docker-container-and-helm-chart-to-gitlab-using-gitlab-ci-operator-framework-kustomize-and-helmify-3j68</guid>
      <description>&lt;p&gt;&lt;a href="https://gitlab.com/stammkneipe.dev/my-operator/-/blob/main/RELEASE.md"&gt;This guide&lt;/a&gt; serves as a direct follow-up to &lt;a href="https://dev.to/patrickdomnick/building-a-kubernetes-operator-with-the-operator-framework-5fh0"&gt;Building a Kubernetes Operator with the Operator Framework&lt;/a&gt;.&lt;br&gt;
It meticulously walks you through the intricacies of packaging a Docker container and Helm Chart, subsequently uploading them to GitLab using GitLab CI, with a keen emphasis on generating semantic version tags. It's noteworthy that nearly all CI templates can be executed locally with minor adjustments to environment variables and utilizing Docker.&lt;/p&gt;
&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before commencing, ensure that you have the following tools installed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/mikefarah/yq"&gt;yq&lt;/a&gt;: &lt;code&gt;brew install yq&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.docker.com/products/docker-desktop"&gt;docker&lt;/a&gt;: &lt;code&gt;brew install docker&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/kubernetes-sigs/kustomize"&gt;kustomize&lt;/a&gt;: &lt;code&gt;brew install kustomize&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/helm/helm"&gt;helm&lt;/a&gt;: &lt;code&gt;brew install helm&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/arttor/helmify"&gt;helmify&lt;/a&gt;: &lt;code&gt;brew install arttor/tap/helmify&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Release Strategy
&lt;/h2&gt;

&lt;p&gt;Before delving into packaging the Docker Container and Helm Chart, let's discuss the strategy in detail. The aim is to create &lt;a href="https://github.com/semantic-release/semantic-release"&gt;Semantic Releases&lt;/a&gt; on a &lt;a href="https://docs.gitlab.com/ee/ci/pipelines/schedules.html"&gt;Pipeline Schedule&lt;/a&gt; (weekly) to eliminate the need for manual releases. These releases will trigger a pipeline running on a tag, responsible for releasing the Helm Chart and Docker Image. Since pushing changes directly to production is not an option, a pipeline for testing new features and bug fixes will also be implemented.&lt;/p&gt;

&lt;p&gt;The workflow unfolds as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a new feature branch (&lt;code&gt;feature/improvement&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Make changes to the code.&lt;/li&gt;
&lt;li&gt;Submit a Merge Request to the Main Branch (&lt;code&gt;feat: Improve Code&lt;/code&gt;). This triggers a pipeline where a Pre-Release is built.&lt;/li&gt;
&lt;li&gt;Merge the changes to the Main branch after thorough testing.&lt;/li&gt;
&lt;li&gt;Wait for the weekly release to increment the version number (specified as feat, resulting in a Minor Release).&lt;/li&gt;
&lt;li&gt;The new tag will publish a Docker Image and Helm Chart to GitLab.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Build the Docker Image
&lt;/h2&gt;

&lt;p&gt;The Operator SDK conveniently provides a Dockerfile, simplifying the building process. We will utilize &lt;a href="https://github.com/GoogleContainerTools/kaniko"&gt;Kaniko&lt;/a&gt; for its user-friendly approach and the fact that it doesn't require a Docker Socket or any form of Docker in Docker. This process ensures the Docker Image is uploaded to the GitLab Container Registry.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;.docker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gcr.io/kaniko-project/executor:v1.20.1-debug&lt;/span&gt;
    &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" &amp;gt; /kaniko/.docker/config.json&lt;/span&gt; &lt;span class="c1"&gt;# Authenticate against Gitlab&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile  --destination $CI_REGISTRY_IMAGE:$TAG&lt;/span&gt; &lt;span class="c1"&gt;# Build and Push the Docker Image&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Helm
&lt;/h2&gt;

&lt;p&gt;Before delving into the process, it's essential to decide where to store Helm Charts within GitLab. Two viable options are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Package Registry&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitLab Pages&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While the Package Registry appears ideal, it comes with a drawback - a somewhat unconventional domain and Helm Chart path (&lt;code&gt;https://gitlab.com/api/v4/projects/{projectId}/packages/helm/stable&lt;/code&gt;). Alternatively, GitLab Pages introduces its own set of challenges. Since Pages primarily deals with static code, properly indexing every version becomes nearly impossible. This guide, however, will focus on indexing the latest version, providing a glimpse into this method.&lt;/p&gt;

&lt;h3&gt;
  
  
  Packaging the Helm Chart
&lt;/h3&gt;

&lt;p&gt;The Operator SDK employs &lt;a href="https://github.com/kubernetes-sigs/kustomize"&gt;Kustomize&lt;/a&gt; instead of Helm, necessitating additional steps to arrive at our Helm Chart. The initial step involves generating the bundle containing our manifests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;.bundle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;quay.io/operator-framework/operator-sdk:v1.32&lt;/span&gt;
    &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;make bundle&lt;/span&gt; &lt;span class="c1"&gt;# Generate the Bundle directory dynamically&lt;/span&gt;
  &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bundle&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ensure to run the make bundle command at least once locally. This ensures the creation of necessary files for automation later on. Additionally, add the bundle/* directory to the .gitignore file. Also, remember to update the Docker Image of your Manager to the actual Docker Image (registry.gitlab.com/path/to/operator).&lt;/p&gt;

&lt;h3&gt;
  
  
  Generate and Upload the Helm Chart
&lt;/h3&gt;

&lt;p&gt;Now that we have the necessary manifests, it's time to transform them into a proper Helm Chart. For this purpose, we will utilize Helmify and a custom Docker Image bundling useful tools for Helm Chart upload. After generating the Helm Chart, YQ will be used to set the version. As noted in the release strategy, there is no distinction between Chart and Docker Version. In other words, the Application Version and Helm Chart Version will always align. In this example, we will publish the Helm Chart to both GitLab Pages and the Package Registry.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;.upload&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helm&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;HELM_EXPERIMENTAL_OCI&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;registry.gitlab.com/stammkneipe.dev/operator-packager:latest&lt;/span&gt;
  &lt;span class="na"&gt;before_script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git config --global --add safe.directory '*'&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;VERSION=$(git describe --tags `git rev-list --tags --max-count=1` 2&amp;gt;/dev/null)&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;if [ -z "$VERSION" ]; then VERSION="0.1.0"; fi&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;if [ -z "$CI_COMMIT_TAG" ]; then VERSION="${VERSION}-${CI_PIPELINE_IID}"; fi&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;kustomize build config/default | helmify ${CI_PROJECT_NAME}&lt;/span&gt; &lt;span class="c1"&gt;# Generate the Helm Chart with Kustomize and Helmify&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;yq e -P ".version = \"${VERSION}\"" -i ${CI_PROJECT_NAME}/Chart.yaml&lt;/span&gt; &lt;span class="c1"&gt;# Set the Version of the Helm Chart&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;yq e -P ".appVersion = \"${VERSION}\"" -i ${CI_PROJECT_NAME}/Chart.yaml&lt;/span&gt; &lt;span class="c1"&gt;# Set the Version of the Application&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;helm package ${CI_PROJECT_NAME} --destination ./public&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;helm repo add --username gitlab-ci-token --password ${CI_JOB_TOKEN} ${CI_PROJECT_NAME} ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/helm/stable&lt;/span&gt; &lt;span class="c1"&gt;# Only for Package Registry&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;helm plugin install https://github.com/chartmuseum/helm-push&lt;/span&gt; &lt;span class="c1"&gt;# Only for Package Registry&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;helm cm-push ./public/${CI_PROJECT_NAME}-${VERSION}.tgz ${CI_PROJECT_NAME}&lt;/span&gt; &lt;span class="c1"&gt;# Only for Package Registry&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;helm repo index --url https://${CI_PROJECT_NAMESPACE}.gitlab.io/${CI_PROJECT_NAME} .&lt;/span&gt; &lt;span class="c1"&gt;# Only for Gitlab Pages&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mv index.yaml ./public&lt;/span&gt; &lt;span class="c1"&gt;# Only for Gitlab Pages&lt;/span&gt;
  &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;public&lt;/span&gt; &lt;span class="c1"&gt;# Gitlab Pages&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Generating the Actual GitLab CI Jobs
&lt;/h2&gt;

&lt;p&gt;Up until now, we've been creating templates for reuse in actual CI Jobs. Now it's time to generate the full [GitLab CI] configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stages
&lt;/h3&gt;

&lt;p&gt;For this pipeline, we only need three stages. The build stages will handle building the Docker Image and the manifests for our Helm Chart, while the release stage will exclusively manage the Semantic Release.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;helm&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;release&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Feature Branches
&lt;/h3&gt;

&lt;p&gt;Feature or Renovate jobs will only run when a Merge Request is made to your Main Branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;.feature_renovate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;TAG&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_COMMIT_REF_SLUG-$CI_PIPELINE_IID&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^renovate/ &amp;amp;&amp;amp; $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^fix/ &amp;amp;&amp;amp; $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^feat/ &amp;amp;&amp;amp; $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^feature/ &amp;amp;&amp;amp; $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH&lt;/span&gt;

&lt;span class="na"&gt;containerize_feature&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.docker&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.feature_renovate&lt;/span&gt;

&lt;span class="na"&gt;bundle_feature&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.bundle&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.feature_renovate&lt;/span&gt;

&lt;span class="na"&gt;upload_feature&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.upload&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.feature_renovate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Main Branch
&lt;/h3&gt;

&lt;p&gt;The Main Branch will serve two purposes. Firstly, we want to create a latest build, and on top of that, we want to generate semantic releases. This requires ensuring that semantic releases only run on a schedule, and other jobs do not run on schedule.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;.latest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;TAG&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;latest&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH &amp;amp;&amp;amp; $CI_PIPELINE_SOURCE != "schedule"&lt;/span&gt;

&lt;span class="na"&gt;containerize_latest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.docker&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.latest&lt;/span&gt;

&lt;span class="na"&gt;bundle_latest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.bundle&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.latest&lt;/span&gt;

&lt;span class="na"&gt;upload_latest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.upload&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.latest&lt;/span&gt;

&lt;span class="na"&gt;release_weekly&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;release&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;registry.gitlab.com/stammkneipe.dev/semantic-release:latest&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git config --global --add safe.directory '*'&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npx -p @semantic-release/changelog -p @semantic-release/exec -p @semantic-release/git -p @semantic-release/gitlab semantic-release&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_PIPELINE_SOURCE == "schedule"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Version Tag
&lt;/h3&gt;

&lt;p&gt;The last jobs will be used for the actual versioned release and only run on a tag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;.tag&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;FULL_IMAGE_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_COMMIT_TAG&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_COMMIT_TAG&lt;/span&gt;

&lt;span class="na"&gt;containerize_tag&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.docker&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.tag&lt;/span&gt;

&lt;span class="na"&gt;bundle_tag&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.bundle&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.tag&lt;/span&gt;

&lt;span class="na"&gt;upload_tag&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.upload&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.tag&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating the Schedule
&lt;/h2&gt;

&lt;p&gt;The only thing left to do is to create a schedule to your liking. Keep in mind that Semantic Release will not create a release when there was no change in your code base. So there is no harm in using a short interval.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;Now you can choose between your latest version inside Pages or use the Registry Package (&lt;code&gt;https://gitlab.com/api/v4/projects/{projectId}/packages/helm/stable&lt;/code&gt;) to install your Helm chart.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By following these steps, you've successfully set up a GitLab CI pipeline for packaging and deploying a Docker container and Helm Chart. The integration of semantic versioning ensures a structured release process for your application. This streamlined workflow enhances collaboration and facilitates the continuous delivery of your containerized applications.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Building a Kubernetes Operator with the Operator Framework</title>
      <dc:creator>Patrick Domnick</dc:creator>
      <pubDate>Sun, 07 Jan 2024 15:46:58 +0000</pubDate>
      <link>https://forem.com/patrickdomnick/building-a-kubernetes-operator-with-the-operator-framework-5fh0</link>
      <guid>https://forem.com/patrickdomnick/building-a-kubernetes-operator-with-the-operator-framework-5fh0</guid>
      <description>&lt;h2&gt;
  
  
  &lt;a href="https://gitlab.com/stammkneipe.dev/my-operator"&gt;Overview&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Kubernetes Operators simplify the management of complex applications on Kubernetes. In this guide, we'll walk through creating a simple Kubernetes Operator using the &lt;a href="https://operatorframework.io/"&gt;Operator Framework&lt;/a&gt;. We'll also cover setting up a local Kubernetes cluster with KIND (Kubernetes in Docker) and deploying the Operator to the KIND cluster.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This guide assumes you know what Kubernetes and Docker are and that you have a Mac or Linux/WSL machine.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You might want to install the following tools on your machine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://golang.org/doc/install"&gt;golang&lt;/a&gt;: &lt;code&gt;brew install go&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.docker.com/products/docker-desktop"&gt;docker&lt;/a&gt;: &lt;code&gt;brew install docker&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://kind.sigs.k8s.io/docs/user/quick-start/"&gt;kind&lt;/a&gt;: &lt;code&gt;brew install kind&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ahmetb/kubectx"&gt;kubectx&lt;/a&gt;: &lt;code&gt;brew install kubectx&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/operator-framework/operator-sdk"&gt;operator-sdk&lt;/a&gt;: &lt;code&gt;brew install operator-sdk&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/kubernetes-sigs/kubebuilder"&gt;kubebuilder&lt;/a&gt;: &lt;code&gt;brew install kubebuilder&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are some extra tools which might be useful in the future:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/derailed/k9s"&gt;k9s&lt;/a&gt;: &lt;code&gt;brew install k9s&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/kubernetes-sigs/kustomize"&gt;kustomize&lt;/a&gt;: &lt;code&gt;brew install kustomize&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/helm/helm"&gt;helm&lt;/a&gt;: &lt;code&gt;brew install helm&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/arttor/helmify"&gt;helmify&lt;/a&gt;: &lt;code&gt;brew install arttor/tap/helmify&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Creating a Kubernetes Cluster with KIND
&lt;/h2&gt;

&lt;p&gt;Before we can deploy our Operator, we need a Kubernetes cluster to deploy it to. We'll use KIND to create a local Kubernetes cluster.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a KIND cluster:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kind create cluster
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure kubectl to use the KIND cluster:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kind &lt;span class="nb"&gt;export &lt;/span&gt;kubeconfig
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Switch to the correct cluster context:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectx kind-kind
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Verify the cluster is running:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl cluster-info
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We now have a minimal local Kubernetes cluster running on our machine. You should be able to use &lt;code&gt;kubectl&lt;/code&gt; or &lt;code&gt;k9s&lt;/code&gt; to interact with the cluster like any other Kubernetes cluster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initializing the Operator Project
&lt;/h2&gt;

&lt;p&gt;We can now build our Operator using the Operator Framework. We'll use the Operator SDK to scaffold a new Operator project and then generate Custom Resource APIs. It is recommended to create a new Git repository for your Operator project and choose a meaningful name for your Operator. You can commit your changes after each CLI command to better understand what the operator is generating.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a new Operator project:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;operator-sdk init &lt;span class="nt"&gt;--plugins&lt;/span&gt; go/v3 &lt;span class="nt"&gt;--repo&lt;/span&gt; github.com/my-group/my-operator
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a new Custom Resource Definition (CRD):&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;operator-sdk create api &lt;span class="nt"&gt;--group&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;example &lt;span class="nt"&gt;--version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;v1alpha1 &lt;span class="nt"&gt;--kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;MyApp
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This should leave you with a ready-to-use Operator project scaffolded by the Operator SDK. The three most important directories are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;api&lt;/code&gt;: Containing the types for your Custom Resources Definition&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;controllers&lt;/code&gt;: Containing the logic for your Operator&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;config/samples&lt;/code&gt;: Containing sample Custom Resource instances&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implementing the Operator
&lt;/h2&gt;

&lt;p&gt;We'll now implement the Operator logic. The Operator Framework provides a high-level API for writing Operators in Go. This operator will watch for instances of the Custom Resource and create a Config Map for each instance. This is just a simple example to help you get started.&lt;/p&gt;

&lt;h3&gt;
  
  
  Defining the Custom Resource
&lt;/h3&gt;

&lt;p&gt;Open the file &lt;a href="//%20raw%20`api/v1alpha1/myapp_types.go`%20endraw%20"&gt;api/v1alpha1/myapp_types.go&lt;/a&gt; and add the following code of the &lt;code&gt;MyAppSpec&lt;/code&gt; struct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// MyAppSpec defines the desired state of MyApp&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;MyAppSpec&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster&lt;/span&gt;
    &lt;span class="c"&gt;// Important: Run "make" to regenerate code after modifying this file&lt;/span&gt;

    &lt;span class="c"&gt;// Name is the name of the config to be created of MyApp.&lt;/span&gt;
    &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"name,omitempty"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will add a &lt;code&gt;Name&lt;/code&gt; field to the Custom Resource Spec. We'll use this field to set the name of the Config Map.&lt;/p&gt;

&lt;p&gt;We can also add a status field to the Custom Resource. This will be used to store the status of the Custom Resource. To do so, add the following code to the &lt;code&gt;MyAppStatus&lt;/code&gt; struct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// MyAppStatus defines the observed state of MyApp&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;MyAppStatus&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster&lt;/span&gt;
    &lt;span class="c"&gt;// Important: Run "make" to regenerate code after modifying this file&lt;/span&gt;
    &lt;span class="n"&gt;Conditions&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;metav1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Condition&lt;/span&gt; &lt;span class="s"&gt;`json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To support visual feedback for the users who are using tools like &lt;a href="https://github.com/MuhammedKalkan/OpenLens"&gt;Openlens&lt;/a&gt;, we can add a &lt;code&gt;+kubebuilder:printcolumn&lt;/code&gt; annotation to the &lt;code&gt;MyApp&lt;/code&gt; struct. To do so, add the following code to the &lt;code&gt;MyApp&lt;/code&gt; struct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// MyApp is the Schema for the myapps API&lt;/span&gt;
&lt;span class="c"&gt;// +kubebuilder:printcolumn:name="Name",type="string",JSONPath=".spec.name",description="The name of the config map to be created"&lt;/span&gt;
&lt;span class="c"&gt;// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type==\"App\")].reason",description="The status of this resource"&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;MyApp&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;metav1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TypeMeta&lt;/span&gt;   &lt;span class="s"&gt;`json:",inline"`&lt;/span&gt;
    &lt;span class="n"&gt;metav1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ObjectMeta&lt;/span&gt; &lt;span class="s"&gt;`json:"metadata,omitempty"`&lt;/span&gt;

    &lt;span class="n"&gt;Spec&lt;/span&gt;   &lt;span class="n"&gt;MyAppSpec&lt;/span&gt;   &lt;span class="s"&gt;`json:"spec,omitempty"`&lt;/span&gt;
    &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="n"&gt;MyAppStatus&lt;/span&gt; &lt;span class="s"&gt;`json:"status,omitempty"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Implementing the Controller
&lt;/h3&gt;

&lt;p&gt;Before we can implement the Controller, we need to add a dependency. To do so, open the file &lt;a href="//controllers/myapp_controller.go"&gt;controllers/myapp_controller.go&lt;/a&gt; and add the following code to the imports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;

    &lt;span class="n"&gt;corev1&lt;/span&gt; &lt;span class="s"&gt;"k8s.io/api/core/v1"&lt;/span&gt;
    &lt;span class="n"&gt;apierrors&lt;/span&gt; &lt;span class="s"&gt;"k8s.io/apimachinery/pkg/api/errors"&lt;/span&gt;
    &lt;span class="s"&gt;"k8s.io/apimachinery/pkg/api/meta"&lt;/span&gt;
    &lt;span class="n"&gt;metav1&lt;/span&gt; &lt;span class="s"&gt;"k8s.io/apimachinery/pkg/apis/meta/v1"&lt;/span&gt;
    &lt;span class="s"&gt;"k8s.io/apimachinery/pkg/runtime"&lt;/span&gt;
    &lt;span class="s"&gt;"k8s.io/apimachinery/pkg/types"&lt;/span&gt;
    &lt;span class="n"&gt;ctrl&lt;/span&gt; &lt;span class="s"&gt;"sigs.k8s.io/controller-runtime"&lt;/span&gt;
    &lt;span class="s"&gt;"sigs.k8s.io/controller-runtime/pkg/client"&lt;/span&gt;
    &lt;span class="s"&gt;"sigs.k8s.io/controller-runtime/pkg/log"&lt;/span&gt;

    &lt;span class="n"&gt;examplev1alpha1&lt;/span&gt; &lt;span class="s"&gt;"github.com/my-group/my-operator/api/v1alpha1"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the file &lt;a href="//controllers/myapp_controller.go"&gt;controllers/myapp_controller.go&lt;/a&gt; and add the following code to the &lt;code&gt;Reconcile&lt;/code&gt; function (&lt;code&gt;// TODO(user): your logic here&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// For more details, check Reconcile and its Result here:&lt;/span&gt;
&lt;span class="c"&gt;// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.1/pkg/reconcile&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;MyAppReconciler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Reconcile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FromContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Get CRD&lt;/span&gt;
    &lt;span class="n"&gt;myApp&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;examplev1alpha1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NamespacedName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;myApp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;apierrors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsNotFound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MyApp not found. Ignoring since object must be deleted."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Failed to get MyApp."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Start the Reconciliation&lt;/span&gt;
    &lt;span class="n"&gt;conditions&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;myApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Conditions&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;conditions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetStatusCondition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conditions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metav1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Condition&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"App"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;metav1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConditionUnknown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Reason&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;"Initializing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Starting reconciliation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Length"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;myApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Conditions&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;myApp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Failed to update MyApp status"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c"&gt;// Start the next cycle&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Act depending on the Condition. This is just a rough example.&lt;/span&gt;
    &lt;span class="n"&gt;currentCondition&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;conditions&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reason&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;currentCondition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"Initializing"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="c"&gt;// Create a ConfigMap&lt;/span&gt;
        &lt;span class="n"&gt;cm&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;corev1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConfigMap&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NamespacedName&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;myApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="n"&gt;Namespace&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;myApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Namespace&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;apierrors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsNotFound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c"&gt;// No ConfigMap exists and we create one&lt;/span&gt;
                &lt;span class="n"&gt;cmInstance&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;corev1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConfigMap&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;ObjectMeta&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;metav1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ObjectMeta&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;myApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;Namespace&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;myApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Namespace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cmInstance&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Failed to create a new  ConfigMap"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Namespace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;myApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Namespace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="s"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="n"&gt;myApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c"&gt;// Some unknown error occurred&lt;/span&gt;
                &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Failed to get ConfigMap"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="s"&gt;"Namespace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;myApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Namespace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;myApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c"&gt;// Update the status&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Config Map created"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetStatusCondition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;myApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Conditions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metav1&lt;/span&gt;    &lt;span class="n"&gt;Condition&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"App"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;metav1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConditionTrue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Reason&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;"Available"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Config Map created"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;myApp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Failed to update status"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"Unavailable"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="c"&gt;// Retry depending on the error&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"Available"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="c"&gt;// Everything is fine&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="c"&gt;// Set State if State was unknown&lt;/span&gt;
        &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetStatusCondition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conditions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metav1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Condition&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"App"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;metav1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConditionUnknown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Reason&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;"Initializing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Starting reconciliation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;myApp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Failed to update myApp status"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Operator will watch the Custom Resources and react to changes. The &lt;code&gt;Reconcile&lt;/code&gt; function will be called for each change. The &lt;code&gt;Reconcile&lt;/code&gt; function will check the status of the Custom Resource and act accordingly. In this example, we'll update the status of the Custom Resource and create a Config Map if it doesn't exist.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a sample Custom Resource Instance
&lt;/h3&gt;

&lt;p&gt;To test our implementation we'll create a sample Custom Resource instance. Open the file &lt;a href="//config/samples/example_v1alpha1_myapp.yaml"&gt;config/samples/example_v1alpha1_myapp.yaml&lt;/a&gt; and add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example.stammkneipe.dev/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MyApp&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app.kubernetes.io/name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp&lt;/span&gt;
    &lt;span class="na"&gt;app.kubernetes.io/instance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp-sample&lt;/span&gt;
    &lt;span class="na"&gt;app.kubernetes.io/part-of&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-operator&lt;/span&gt;
    &lt;span class="na"&gt;app.kubernetes.io/managed-by&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kustomize&lt;/span&gt;
    &lt;span class="na"&gt;app.kubernetes.io/created-by&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-operator&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp-sample&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp-config-map&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing the Operator locally
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Deploy the CRDs to the cluster:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Deploy the sample Custom Resource instance to the cluster:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; config/samples
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Start the Operator:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make run
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You should now see the Operator's logs in your terminal. The final message should be &lt;code&gt;Config Map created&lt;/code&gt;. You can now stop the operator and check your cluster for the Config Map:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get configMap myapp-sample &lt;span class="nt"&gt;-o&lt;/span&gt; yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Congratulations! You've successfully built a Kubernetes Operator using the Operator Framework on a local KIND cluster. You can extend this example by adding more features to your Operator and exploring advanced Operator Framework capabilities. I encourage you to play around with the Operator Framework and explore the possibilities of Operators on Kubernetes.&lt;/p&gt;

&lt;p&gt;As you might have noticed, this Tutorial does not include any tests, packaging, or deploying the Operator to a real Kubernetes cluster. I'll cover these topics in future guides. So stay tuned!&lt;/p&gt;

</description>
      <category>docker</category>
      <category>kubernetes</category>
      <category>go</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Using GitLab Kubernetes Runners to Build Melange Packages</title>
      <dc:creator>Patrick Domnick</dc:creator>
      <pubDate>Thu, 28 Dec 2023 10:06:07 +0000</pubDate>
      <link>https://forem.com/patrickdomnick/using-gitlab-kubernetes-runners-to-build-melange-packages-365b</link>
      <guid>https://forem.com/patrickdomnick/using-gitlab-kubernetes-runners-to-build-melange-packages-365b</guid>
      <description>&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;Recently, I came across &lt;a href="https://www.chainguard.dev/"&gt;Chainguard&lt;/a&gt; and wrote the article &lt;a href="https://dev.to/patrickdomnick/building-a-go-package-with-melange-and-a-docker-image-with-apko-141c"&gt;How to build Docker Images with Melange and Apko&lt;/a&gt;. As a fervent supporter of Kubernetes and GitLab CI, I was eager to experiment with building images using &lt;a href="https://github.com/chainguard-dev/melange/tree/main"&gt;Melange&lt;/a&gt; in this particular setup. GitLab's shared Runners work seamlessly with &lt;a href="https://github.com/containers/bubblewrap"&gt;Bubblewrap&lt;/a&gt;, eliminating the need for additional configurations. This post is intended for enthusiasts like myself, interested in hosting their own Kubernetes Runners and leveraging the Kubernetes Runner Type of Melange.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the Kubernetes Executor of Melange
&lt;/h2&gt;

&lt;p&gt;Melange offers four Runner Types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bubblewrap (default)&lt;/li&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;Lima&lt;/li&gt;
&lt;li&gt;Kubernetes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each has its own security implications and drawbacks. I prefer Docker/Lima for local builds and Kubernetes in a CI environment running on a Kubernetes Cluster. Let's explore how to set this up.&lt;/p&gt;

&lt;p&gt;When invoking Melange with the &lt;code&gt;--runner Kubernetes&lt;/code&gt; flag, Melange will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check if you have sufficient permissions to create pods in Kubernetes (RBAC).&lt;/li&gt;
&lt;li&gt;Build a temporary Docker Image and push it to ttl.sh.&lt;/li&gt;
&lt;li&gt;Run this Docker Image as a Pod in Kubernetes and build the package.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Remember, this is a high-level overview to understand the necessary configurations and not an explanation of the actual &lt;a href="https://github.com/chainguard-dev/melange/blob/main/pkg/container/kubernetes_runner.go"&gt;Kubernetes Runner&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring the GitLab Runner
&lt;/h2&gt;

&lt;p&gt;Now that we understand how the Melange Runner works, let's configure our GitLab Runner accordingly. Melange doesn't work with the out-of-the-box Role-Based Access Control (RBAC) Policy of the GitLab Runner. When using GitLab CI with Kubernetes, a Pod (from a Deployment) manages creating Pods for each Job in your GitLab CI Pipeline. This Service Account has the permissions to interact with pods, precisely what we need for Melange. However, Jobs started by this Orchestrator Deployment will only have the &lt;code&gt;default&lt;/code&gt; ServiceAccount attached to it. This is fine for normal CI work but not for Melange, as it needs to create new pods and interact with them.&lt;/p&gt;

&lt;p&gt;You can change this by modifying the &lt;code&gt;config.template.toml&lt;/code&gt; Configmap or the &lt;code&gt;runners.config&lt;/code&gt; in the &lt;code&gt;values.yaml&lt;/code&gt;. Simply set the &lt;code&gt;service_account&lt;/code&gt; for your Kubernetes Runner to the ServiceAccount of the Orchestrator. Here's an example if you named your Helm Release and thus the ServiceAccount &lt;code&gt;gitlab-runner&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;runners&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;[[runners]]&lt;/span&gt;
      &lt;span class="s"&gt;[runners.kubernetes]&lt;/span&gt;
        &lt;span class="s"&gt;namespace = "{{.Release.Namespace}}"&lt;/span&gt;
        &lt;span class="s"&gt;service_account = "gitlab-runner"&lt;/span&gt;
        &lt;span class="s"&gt;image = "alpine"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember: This has significant security implications. Now, every Job could start background Pods in your Cluster. While this might seem like a significant risk, anyone with access to your repository could do this anyway.&lt;/p&gt;

&lt;p&gt;A small extra note: This is the lazy way to do it without the need to deploy any new ServiceAccount and other RBAC Resources. We just need to tweak one little line in our &lt;code&gt;values.yaml&lt;/code&gt; file, which I find convenient. If you find a better and more secure way, please let me know and leave a comment!&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring Melange
&lt;/h2&gt;

&lt;p&gt;When using the Kubernetes Runner, Melange looks for a config file named &lt;code&gt;.melange.k8s.yaml&lt;/code&gt;. Here you can specify the Namespace of the pod, the container Registry to be used, and other Kubernetes Specs. By default, Melange will attempt to start a pod in the &lt;code&gt;default&lt;/code&gt; Namespace. However, since no sane person uses the default namespace, we can easily change it to the Namespace of our GitLab Runner.&lt;/p&gt;

&lt;p&gt;While we're at it, we can also change the Registry and Repository to the GitLab Registry instead of using ttl.sh. We don't want everyone to have access to our super secure project, do we?&lt;/p&gt;

&lt;p&gt;Let's assume our GitLab Runner is running in a namespace called &lt;code&gt;stammkneipe-dev&lt;/code&gt;, and our project has the following path &lt;code&gt;stammkneipe.dev/golang-apko-example&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stammkneipe-dev&lt;/span&gt;
&lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;registry.gitlab.com/stammkneipe.dev/gitlab-runner-operator&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuring GitLab CI
&lt;/h2&gt;

&lt;p&gt;Now we are finally ready to use Melange with the Kubernetes Executor in GitLab CI. The only thing left to do is to ensure that Melange has the permission to push images to the GitLab Registry, since we changed the default from &lt;code&gt;ttl.sh&lt;/code&gt; to &lt;code&gt;registry.gitlab.com&lt;/code&gt;. GitLab has some &lt;a href="https://docs.gitlab.com/ee/ci/variables/predefined_variables.html"&gt;predefined Variables&lt;/a&gt; that can be used to authenticate against our Registry:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CI_REGISTRY&lt;/li&gt;
&lt;li&gt;CI_REGISTRY_USER&lt;/li&gt;
&lt;li&gt;CI_REGISTRY_PASSWORD&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our final build job would look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;package&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cgr.dev/chainguard/melange:latest&lt;/span&gt;
    &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;before_script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cat ${MELANGE_RSA} &amp;gt; melange.rsa&lt;/span&gt; &lt;span class="c1"&gt;# Assuming you have set a Melange RSA Key as a GitLab CI Variable&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mkdir&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;~/.docker&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;echo&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-n&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"{\"auths\":&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{\"${CI_REGISTRY}\":&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{\"auth\":&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;\"$(echo&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-n&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;${CI_REGISTRY_USER}:${CI_REGISTRY_PASSWORD}&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;base64)\"}}}"&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;~/.docker/config.json'&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;melange build --workspace-dir $CI_PROJECT_DIR --runner kubernetes --signing-key melange.rsa melange.yml&lt;/span&gt;
  &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./packages&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion and TL:DR
&lt;/h2&gt;

&lt;p&gt;To use the Kubernetes Executor of Melange with GitLab CI and the Registry, you need to do the following three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set the &lt;code&gt;service_account&lt;/code&gt; for the &lt;code&gt;runners.config&lt;/code&gt; in the &lt;code&gt;[runners.kubernetes]&lt;/code&gt; section to the orchestrator Service Account.&lt;/li&gt;
&lt;li&gt;Create &lt;code&gt;.melange.k8s.yaml&lt;/code&gt; with your &lt;code&gt;namespace&lt;/code&gt; set to the GitLab Runner Namespace and the &lt;code&gt;repo&lt;/code&gt; set to the GitLab CI Repo (&lt;code&gt;registry.gitlab.com/&amp;lt;CI_PROJECT_PATH&amp;gt;&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Authenticate against the GitLab Registry: &lt;code&gt;'mkdir ~/.docker &amp;amp;&amp;amp; echo -n "{\"auths\": {\"${CI_REGISTRY}\": {\"auth\": \"$(echo -n ${CI_REGISTRY_USER}:${CI_REGISTRY_PASSWORD} | base64)\"}}}" &amp;gt; ~/.docker/config.json'&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now you should be able to use Melange without the need for any external Registries and using your Kubernetes Cluster to build packages.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>devops</category>
      <category>kubernetes</category>
      <category>docker</category>
    </item>
    <item>
      <title>Building a Go Package with Melange and a Docker Image with Apko</title>
      <dc:creator>Patrick Domnick</dc:creator>
      <pubDate>Thu, 21 Dec 2023 12:52:04 +0000</pubDate>
      <link>https://forem.com/patrickdomnick/building-a-go-package-with-melange-and-a-docker-image-with-apko-141c</link>
      <guid>https://forem.com/patrickdomnick/building-a-go-package-with-melange-and-a-docker-image-with-apko-141c</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;In this tutorial, we will learn how to create a Go package with Melange, build a Docker with Apko image, and showcase examples using GitLab CI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we begin, make sure you have the following tools installed on your system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/get-docker/"&gt;Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://go.dev/doc/install"&gt;Golang&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/chainguard-dev/melange"&gt;Melange&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/chainguard-dev/apko"&gt;Apko&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Keyfile Generation
&lt;/h2&gt;

&lt;p&gt;To use Melange and Apko, we need to create a private and public key pair to sign our artifacts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;melange keygen
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should now see a &lt;code&gt;melange.rsa&lt;/code&gt; and &lt;code&gt;melange.rsa.pub&lt;/code&gt; file in your directory. These files should not be committed and added to the &lt;code&gt;.gitignore&lt;/code&gt; file just like the &lt;code&gt;/packages&lt;/code&gt;.&lt;br&gt;
This directory will be created ones we run melange to build our Go Application.&lt;/p&gt;

&lt;p&gt;Additionally create a sbom directory with a &lt;code&gt;.gitkeep&lt;/code&gt; file and add &lt;code&gt;sbom/sbom-*.*&lt;/code&gt; and &lt;code&gt;image.tar&lt;/code&gt; to the &lt;code&gt;.gitignore&lt;/code&gt; file as they will become relevant once we create the Docker image.&lt;/p&gt;
&lt;h2&gt;
  
  
  Creating a Go Package with Melange
&lt;/h2&gt;

&lt;p&gt;Initialize a Go module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go mod init gitlab.com/your-username/golang-apko-example
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please replace &lt;code&gt;your-username&lt;/code&gt; with your actual GitLab username in the &lt;code&gt;go mod init&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;Create your Go source code files and write your package logic.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;melange.yml&lt;/code&gt; file in the root of your package directory. This file will define the build configuration for your package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;package&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;golang-apko-example&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.1.0&lt;/span&gt;
  &lt;span class="na"&gt;epoch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build a golang application with melange&lt;/span&gt;
  &lt;span class="na"&gt;copyright&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;license&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MIT&lt;/span&gt;
  &lt;span class="na"&gt;target-architecture&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;x86_64&lt;/span&gt;

&lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;repositories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;https://packages.wolfi.dev/os&lt;/span&gt;
    &lt;span class="na"&gt;keyring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;https://packages.wolfi.dev/os/wolfi-signing.rsa.pub&lt;/span&gt;

&lt;span class="na"&gt;pipeline&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go/build&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;modroot&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;golang-apko-example&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;strip&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Customize the &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;version&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; fields according to your package's requirements. Also, make sure you check the name of the output.&lt;/p&gt;

&lt;p&gt;Build your package locally with Melange:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;melange build &lt;span class="nt"&gt;--signing-key&lt;/span&gt; melange.rsa &lt;span class="nt"&gt;--runner&lt;/span&gt; docker melange.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will generate an &lt;code&gt;APKINDEX.json&lt;/code&gt;, &lt;code&gt;APKINDEX.tar.gz&lt;/code&gt; and &lt;code&gt;.apk&lt;/code&gt; file in your packages directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a Docker Image with apko.yml
&lt;/h2&gt;

&lt;p&gt;Create an &lt;code&gt;apko.yml&lt;/code&gt; file in the root of your package directory. This file will define the Docker image build configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;keyring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;https://packages.wolfi.dev/os/wolfi-signing.rsa.pub&lt;/span&gt;
  &lt;span class="na"&gt;repositories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;https://packages.wolfi.dev/os&lt;/span&gt;
  &lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;wolfi-base&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ca-certificates-bundle&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;golang-apko-example@local&lt;/span&gt;

&lt;span class="na"&gt;accounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;groups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;groupname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nonroot&lt;/span&gt;
      &lt;span class="na"&gt;gid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;65532&lt;/span&gt;
  &lt;span class="na"&gt;users&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nonroot&lt;/span&gt;
      &lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;65532&lt;/span&gt;
      &lt;span class="na"&gt;gid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;65532&lt;/span&gt;
  &lt;span class="na"&gt;run-as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;65532&lt;/span&gt;

&lt;span class="na"&gt;archs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;amd64&lt;/span&gt;

&lt;span class="na"&gt;cmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/usr/bin/golang-apko-example&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important line is the &lt;code&gt;golang-apko-example@local&lt;/code&gt; package signaling a local package should be used instead of the wolfi packages.&lt;/p&gt;

&lt;p&gt;Build the Image with your local repository appended:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apko build &lt;span class="nt"&gt;--debug&lt;/span&gt; &lt;span class="nt"&gt;--sbom-path&lt;/span&gt; ./sbom/ &lt;span class="nt"&gt;--repository-append&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/packages &lt;span class="nt"&gt;--keyring-append&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;melange.rsa.pub apko.yml golang-apko-example:latest image.tar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will build a Docker image as an image.tar with our local package and create SBOM files for our image.&lt;/p&gt;

&lt;p&gt;To actually use the image locally we have to load and execute it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker load &lt;span class="nt"&gt;--input&lt;/span&gt; image.tar
docker run &lt;span class="nt"&gt;-it&lt;/span&gt; golang-apko-example:latest-amd64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using Gitlab CI
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Environment Variables
&lt;/h3&gt;

&lt;p&gt;With Gitlab CI we can automate this process and make it even better.&lt;br&gt;
However, before we can begin we have to save the &lt;code&gt;melange.rsa&lt;/code&gt; and &lt;code&gt;melange.rsa.pub&lt;/code&gt; as Gitlab CI Environment Files so we can use them. To do this simply go to &lt;code&gt;Settings&lt;/code&gt; --&amp;gt; &lt;code&gt;CI/CD&lt;/code&gt; --&amp;gt; &lt;code&gt;Variables&lt;/code&gt; and &lt;code&gt;Add variable&lt;/code&gt; of the Type &lt;code&gt;File&lt;/code&gt; our two files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MELANGE_RSA: File content of &lt;code&gt;melange.rsa&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;MELANGE_PUB: File content of &lt;code&gt;melange.rsa.pub&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You might want to store your certificates in a safer location then Gitlab Environments but lets just stick with this for now.&lt;/p&gt;
&lt;h3&gt;
  
  
  Gitlab CI Preparation
&lt;/h3&gt;

&lt;p&gt;Now we can create a &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; file in the root of your GitLab repository.&lt;/p&gt;

&lt;p&gt;At first we should define some general structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;containerize&lt;/span&gt;

&lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;APKO_FILE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apko.yml"&lt;/span&gt;
  &lt;span class="na"&gt;MELANGE_FILE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;melange.yml"&lt;/span&gt;
  &lt;span class="na"&gt;FULL_IMAGE_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_REGISTRY_IMAGE:latest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just like in our CLI we have 2 Steps to complete:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build the Golang Application&lt;/li&gt;
&lt;li&gt;Build the Docker image&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Currently we simply create a Docker image without any versioning: The name of our image will be the name of the repository name including the path and using latest as the tag.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building the Application with Melange
&lt;/h3&gt;

&lt;p&gt;We can use the same command to build the application in Gitlab CI as we did locally. We just have to make sure that our melange.rsa key exists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;build_package&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cgr.dev/chainguard/melange:latest&lt;/span&gt;
    &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;before_script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;melange version&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cat ${MELANGE_RSA} &amp;gt; melange.rsa&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;melange build --signing-key melange.rsa "${MELANGE_FILE}"&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH &amp;amp;&amp;amp; $CI_PIPELINE_SOURCE != "schedule"&lt;/span&gt;
  &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./packages&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since the default entrypoint of melange is melange itself we have to overwrite to entrypoint so that we can use it in Gitlab CI.&lt;br&gt;
After that we make sure that our key exists and can simply build our application.&lt;br&gt;
The only thing left to do is store the &lt;code&gt;packages&lt;/code&gt; directory as an artifact which can use in the next step.&lt;/p&gt;
&lt;h3&gt;
  
  
  Publishing the Docker Image with Apko
&lt;/h3&gt;

&lt;p&gt;We now need to build and publish the Docker Image to the Gitlab Registry. In this step we will need the public key instead of the private key. In addition we can use the predefined Registry Variables to log into the Gitlab Registry and publish the image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;containerize_package&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;containerize&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;registry.gitlab.com/stammkneipe.dev/apko-ci:latest&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;CI_BUILDS_DIR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_BUILDS_DIR&lt;/span&gt;
  &lt;span class="na"&gt;before_script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apko version&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apko login -u "${CI_REGISTRY_USER}" -p "${CI_REGISTRY_PASSWORD}" "${CI_REGISTRY}"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cat ${MELANGE_PUB} &amp;gt; melange.rsa.pub&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apko publish --debug --sbom-path $CI_PROJECT_DIR/sbom/ --repository-append $(pwd)/packages --keyring-append melange.rsa.pub "${APKO_FILE}" "${FULL_IMAGE_NAME}"&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH &amp;amp;&amp;amp; $CI_PIPELINE_SOURCE != "schedule"&lt;/span&gt;
  &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sbom/*"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will result in a Docker Image directly instead of a tar file. You should be able to see your docker image in the Gitlab Registry (/container_registry/) with information on how to use it.&lt;br&gt;
In addition we can see the SBOM files in the artifacts of the pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;See the full example &lt;a href="https://gitlab.com/stammkneipe.dev/golang-apko-example"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>go</category>
      <category>docker</category>
      <category>cybersecurity</category>
    </item>
    <item>
      <title>Accessing external Secrets in Gitlab CI</title>
      <dc:creator>Patrick Domnick</dc:creator>
      <pubDate>Sun, 15 May 2022 17:32:22 +0000</pubDate>
      <link>https://forem.com/patrickdomnick/accessing-external-secrets-in-gitlab-ci-2een</link>
      <guid>https://forem.com/patrickdomnick/accessing-external-secrets-in-gitlab-ci-2een</guid>
      <description>&lt;p&gt;There are times when you want to access your existing credentials from a secure location, like &lt;a href="https://www.vaultproject.io/"&gt;Vault by HashiCorp&lt;/a&gt;, &lt;a href="https://aws.amazon.com/"&gt;AWS&lt;/a&gt; or some other big Cloud provider but your current CI Image does not have the necessary SDK installed or means to retrieve them. Maintaining them a second time as Gitlab Variables or accessing them in a previous job should not be done and is considered insecure for many reasons. That is why I created &lt;a href="https://gitlab.com/PatrickDomnick/gin-vals"&gt;Gin Vals&lt;/a&gt;. Simply run Gin Vals as an Service and make a Web Request to the service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Concept
&lt;/h2&gt;

&lt;p&gt;As the name describes Gin Vals combines to simple GO dependencies to create a slim and easy solution for most providers.&lt;br&gt;
&lt;a href="https://github.com/variantdev/vals"&gt;Vals by Variantdev&lt;/a&gt; is a tool for managing configuration values and secrets for the major cloud providers and other technologies.&lt;br&gt;
Now we simply need to make it accessible via REST with the Gin &lt;a href="https://github.com/gin-gonic/gin"&gt;Web Framework&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;Because we are using Vals as our configuration and secrets tool, we can simply refer to its &lt;a href="https://github.com/variantdev/vals#supported-backends"&gt;documentation&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vault: &lt;code&gt;ref+vault://PATH/TO/KVBACKEND[?address=VAULT_ADDR:PORT&amp;amp;token_file=PATH/TO/FILE&amp;amp;token_env=VAULT_TOKEN]#/fieldkey&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;AWS Secrets Manager: &lt;code&gt;ref+awssecrets://PATH/TO/SECRET[?region=REGION&amp;amp;version_stage=STAGE&amp;amp;version_id=ID]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;GCP Secrets Manager: &lt;code&gt;ref+gcpsecrets://PROJECT/SECRET[?version=VERSION]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;and many more...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I will use the simple &lt;code&gt;echo&lt;/code&gt; method to display some possible methods of using Gin Vals.&lt;/p&gt;
&lt;h3&gt;
  
  
  Simple single value GET request
&lt;/h3&gt;

&lt;p&gt;The easiest way is to just retrieve one secret via GET. Add the Vals string as path and you should be able to get your secret value into a variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bitnami/bitnami-shell:latest&lt;/span&gt;
  &lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;patrickdomnick/gin-vals:latest&lt;/span&gt;
      &lt;span class="na"&gt;alias&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ginvals&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export secret=$(curl -X GET "http://ginvals:9090/ref+echo://foo/bar")&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo secret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Simple single value POST request
&lt;/h3&gt;

&lt;p&gt;The more advanced method would be to retrieve many secrets at once as a json object. From here we could parse the data with tools like jq depending on the main image you are using.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bitnami/bitnami-shell:latest&lt;/span&gt;
  &lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;patrickdomnick/gin-vals:latest&lt;/span&gt;
      &lt;span class="na"&gt;alias&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ginvals&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export secretJson=$(curl -H 'Content-Type&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json' -d '{"foo"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ref+echo://foo/bar"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bar"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ref+echo://bar/foo"&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-X&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"http://ginvals:9090")&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>go</category>
      <category>security</category>
      <category>docker</category>
    </item>
  </channel>
</rss>
