<?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: Sherine Khoury</title>
    <description>The latest articles on Forem by Sherine Khoury (@sherinek).</description>
    <link>https://forem.com/sherinek</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%2F398363%2F14b941d2-2687-4690-96b0-8c3961266a78.jpeg</url>
      <title>Forem: Sherine Khoury</title>
      <link>https://forem.com/sherinek</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/sherinek"/>
    <language>en</language>
    <item>
      <title>Sending Tekton pipeline status to Matrix chat room</title>
      <dc:creator>Sherine Khoury</dc:creator>
      <pubDate>Fri, 19 Jul 2024 13:56:22 +0000</pubDate>
      <link>https://forem.com/sherinek/sending-tekton-pipeline-status-to-matrix-chat-room-m9d</link>
      <guid>https://forem.com/sherinek/sending-tekton-pipeline-status-to-matrix-chat-room-m9d</guid>
      <description>&lt;p&gt;Matrix provides an "open network for secure, decentralised communication". With Matrix &lt;a href="https://matrix.org/ecosystem/bridges/" rel="noopener noreferrer"&gt;bridges&lt;/a&gt;, you can connect to Slack, IRC, Teams, Discord, you name it!&lt;/p&gt;

&lt;p&gt;This blog post explains how to interconnect a Tekton pipeline with a Matrix room, for the purpose of notifying the room members of the pipeline status.&lt;/p&gt;

&lt;h2&gt;
  
  
  Matrix API
&lt;/h2&gt;

&lt;p&gt;Matrix provides several APIs, &lt;a href="https://spec.matrix.org/v1.4/client-server-api/" rel="noopener noreferrer"&gt;Client-Server API&lt;/a&gt; is one of them.&lt;/p&gt;

&lt;p&gt;In order to be able to send messages to a matrix room, the Tekton pipeline needs to send an &lt;code&gt;access_token&lt;/code&gt; along with the request to the Matrix server.&lt;/p&gt;

&lt;p&gt;Thus, a prerequisite to this task is to register a Matrix user for the bot with one of the Matrix servers. &lt;/p&gt;

&lt;h3&gt;
  
  
  Obtaining a Matrix &lt;code&gt;access_token&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Once the registration process is done, I obtained the &lt;code&gt;access_token&lt;/code&gt; through a simple login API call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; curl -XPOST -d '{"type":"m.login.password", "user":"sherine.khoury", "password":"take_a_wild_guess"}' "https://matrix.org/_matrix/client/r0/login"
{"user_id":"@sherine.khoury:matrix.org","access_token":"syt_c2hlcmluZS5raG91cnk_NFpzzGCtxFAHEDVKhYTl_123456","home_server":"matrix.org","device_id":"CNYGHLSLQY","well_known":{"m.homeserver":{"base_url":"https://matrix-client.matrix.org/"}}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the &lt;code&gt;access_token&lt;/code&gt;, I created a secret, with name &lt;code&gt;matrix-access-token&lt;/code&gt; in the namespace hosting my pipeline, of type generic with a single key, &lt;code&gt;token&lt;/code&gt;, containing the &lt;code&gt;access_token&lt;/code&gt; I just obtained in the step above.&lt;/p&gt;

&lt;p&gt;Example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kind: Secret
apiVersion: v1
metadata:
  name: matrix-access-token
stringData:
  token: {OAuth token for the bot app}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Obtaining the Matrix Room ID
&lt;/h3&gt;

&lt;p&gt;The hardest for me was to create a room with Matrix. I'm pretty new to Matrix 😛, and it seems every server has different rules when it comes to the request body of &lt;a href="https://spec.matrix.org/v1.4/client-server-api/#creation" rel="noopener noreferrer"&gt;&lt;code&gt;POST /_matrix/client/v3/createRoom&lt;/code&gt;&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Similarly, every Matrix client calls the room differently: On FluffyChat, it's a Group, which can be created after creating a space. In Element, it's simply called a room!! Thankfully. &lt;/p&gt;

&lt;p&gt;When you create a room in Element, the web UI provides the room ID back to you. &lt;/p&gt;

&lt;p&gt;But you still need to set a Local address for the room in the &lt;code&gt;Room Settings&lt;/code&gt; in order to be able to query the roomAlias by API. 🤷 &lt;/p&gt;

&lt;p&gt;The room alias usually is #ROOM_NAME:SERVER_NAME.&lt;/p&gt;

&lt;p&gt;So for a room with name my-team on matrix.org, you can also obtain the RoomID by sending the following request to the client-server API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -v https://matrix.org/_matrix/client/v3/directory/room/%23my-room:matrix.org
{"room_id":"!xWldUqOaDUkrHUZRIP:matrix.org","servers":["matrix.org"]}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice there is no need for authentication here. Notice also the &lt;code&gt;%23&lt;/code&gt; replacing the &lt;code&gt;#&lt;/code&gt; character in the room alias.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tekton Task
&lt;/h2&gt;

&lt;p&gt;Largely inspired from the &lt;a href="https://raw.githubusercontent.com/tektoncd/catalog/main/task/send-to-channel-slack/0.1/send-to-channel-slack.yaml" rel="noopener noreferrer"&gt;Slack task&lt;/a&gt; on TektonHub:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: send-to-matrix-room
  labels:
    app.kubernetes.io/version: "0.1"
  annotations:
    tekton.dev/pipelines.minVersion: "0.12.1"
    tekton.dev/categories: Messaging
    tekton.dev/tags: messaging
    tekton.dev/platforms: "linux/amd64,linux/s390x,linux/ppc64le"
spec:
  description: &amp;gt;-
    These tasks post a simple message to a matrix room.

    This task uses Matrix's Client-Server REST api to send the message.

  params:
  - name: matrix-access-token
    type: string
    description: secret name containing matrix access token (key is token)
  - name: room
    type: string
    description: room id (in the format !&amp;lt;ROOM_ID&amp;gt;:&amp;lt;SERVER_NAME&amp;gt;)
  - name: endpoint
    type: string
    description: Matrix server URL to which to send the message
  - name: message
    type: string
    description: plain text message
  steps:
  - name: post
    image: docker.io/curlimages/curl:7.70.0@sha256:031df77a11e5edded840bc761a845eab6e3c2edee22669fb8ad6d59484b6a1c4 #tag: 7.70.0
    script: |
      #!/bin/sh
      /usr/bin/curl -X POST -H 'Content-type: application/json' --data '{"msgtype":"m.text", "body":"$(params.message)"}' https://$(params.endpoint)/_matrix/client/r0/rooms/$(params.room)/send/m.room.message?access_token=$TOKEN
    env:
    - name: TOKEN
      valueFrom:
        secretKeyRef:
          name: $(params.matrix-access-token)
          key: token

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 It could be an idea to replace &lt;code&gt;room&lt;/code&gt; and &lt;code&gt;endpoint&lt;/code&gt; by the room alias.&lt;br&gt;
Instead of having a single step I could have used:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use &lt;code&gt;cut -d\: -f2&lt;/code&gt;, I could retrieve the endpoint&lt;/li&gt;
&lt;li&gt;Curl the &lt;code&gt;/_matrix/client/v3/directory/room&lt;/code&gt; to retrieve the RoomID&lt;/li&gt;
&lt;li&gt;Send the message using the RoomID and Endpoint&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;💡 If this looks good, I could send a PR to the TektonHub.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Tekton example pipeline
&lt;/h2&gt;

&lt;p&gt;Now that I've got a task, I can use that task in a pipeline, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: pipeline-matrix-hello
  namespace: okd-team
spec:
  params:
  - name: matrix-room
    description: room id (in the format !&amp;lt;ROOM_ID&amp;gt;:&amp;lt;SERVER_NAME&amp;gt;)
    type: string
  - name: matrix-endpoint
    description: room id (in the format !&amp;lt;ROOM_ID&amp;gt;:&amp;lt;SERVER_NAME&amp;gt;)
    type: string

  - name: repo-name 
    description: The repo name
    type: string
  - name: bundle-version 
    description: The bundle version
    type: string

  tasks:
  - name: talk-to-matrix
    params:
      - name: matrix-access-token
        value: matrix-access-token
      - name: endpoint
        value: $(params.matrix-endpoint)
      - name: room
        value: $(params.matrix-room)
      - name: message
        value: Going to run operator pipeline on repo $(params.repo-name) to build $(params.bundle-version)
    taskRef:
      kind: Task
      name: send-to-matrix-room

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, start the pipeline using&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tkn pipeline start pipeline-matrix-hello --param repo-name=node-observability-operator --param bundle-version=v1.1.1 --param matrix-endpoint=matrix.org --param matrix-room=\!yKXXPqFwfCOTipZMxp:matrix.org
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The logs of the pipelinerun show:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tkn pipelinerun logs pipeline-matrix-hello-run-hscx9 -f -n okd-team
[talk-to-matrix : post]   % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
[talk-to-matrix : post]                                  Dload  Upload   Total   Spent    Left  Speed
100   172    0    59  100   113    114    219 --:--:-- --:--:-- --:--:--   334
[talk-to-matrix : post] {"event_id":"$EYhJ9A5DlMlBx8xD4ORF9q-8r1KmLTsvODvCgwu5xkU"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>cicd</category>
      <category>tekton</category>
      <category>matrix</category>
      <category>devops</category>
    </item>
    <item>
      <title>A gopher’s journey to the center of container images</title>
      <dc:creator>Sherine Khoury</dc:creator>
      <pubDate>Thu, 23 Nov 2023 15:17:30 +0000</pubDate>
      <link>https://forem.com/sherinek/a-gophers-journey-to-the-center-of-container-images-12g2</link>
      <guid>https://forem.com/sherinek/a-gophers-journey-to-the-center-of-container-images-12g2</guid>
      <description>&lt;h2&gt;
  
  
  Blissful past...
&lt;/h2&gt;

&lt;p&gt;A couple of years ago, I would never have thought that I would get that interested in the underlying structure of containers, not to mention going into the journey of building one in Golang.&lt;/p&gt;

&lt;p&gt;I was living the blissful life of an engineer who simply uses &lt;code&gt;podman pull&lt;/code&gt; or &lt;code&gt;docker push&lt;/code&gt;, creates &lt;code&gt;ContainerFile&lt;/code&gt; or &lt;code&gt;Dockerfile&lt;/code&gt;, then runs command lines to build images from such files... sitting back and watching the standard output list the layers being built, then pushed one by one with nice digests to the registry of my choosing, under the tag of my choosing.&lt;/p&gt;

&lt;h2&gt;
  
  
  All changed when...
&lt;/h2&gt;

&lt;p&gt;All this changed when I started contributing to &lt;a href="https://github.com/openshift/oc-mirror"&gt;oc-mirror&lt;/a&gt;, around a year ago. oc-mirror is a plugin of OpenShift's CLI, and targets disconnected clusters. It mirrors all images needed by such clusters in order to install and upgrade OpenShift as well as all its Day-2 operators from operator catalogs. &lt;br&gt;
Suddenly, the underground world of containers unraveled. &lt;/p&gt;

&lt;p&gt;Most of the logic of oc-mirror is about extracting metadata from images such as release images and operator catalog images, interpreting the contents of these images in order to determine the list of images that constitute a release or an operator, and later copy those images to an archive or to a partially disconnected registry.&lt;/p&gt;

&lt;p&gt;Nevertheless, some of the activities also include building multi-arch images. This is the case for the graph image. Without going into the details of &lt;a href="https://docs.openshift.com/container-platform/4.14/updating/updating-restricted-network-cluster/restricted-network-update-osus.html"&gt;what this image is useful for&lt;/a&gt;, let's just say that the graph image is simply a UBI9 image, to which we copy some metadata in &lt;code&gt;/var/lib&lt;/code&gt;, and whose &lt;code&gt;CMD&lt;/code&gt; we modify, so that this image can become an init container for the disconnected cluster to use.&lt;/p&gt;
&lt;h2&gt;
  
  
  Let's start building
&lt;/h2&gt;

&lt;p&gt;In this article, we are roughly trying to build in Golang the equivalent of this very simple Containerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM registry.access.redhat.com/ubi9/ubi:latest

RUN curl -L -o cincinnati-graph-data.tar.gz https://api.openshift.com/api/upgrades_info/graph-data

RUN mkdir -p /var/lib/cincinnati-graph-data &amp;amp;&amp;amp; tar xvzf cincinnati-graph-data.tar.gz -C /var/lib/cincinnati-graph-data/ --no-overwrite-dir --no-same-owner

CMD ["/bin/bash", "-c" ,"exec cp -rp /var/lib/cincinnati-graph-data/* /var/lib/cincinnati/graph-data"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'll try to describe the three paths I explored to achieve this task. I'm aware these are probably not the only possibilities, and probably not always adapted to what your context is: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Context A - having root capabilities: using &lt;code&gt;containers/buildah&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Context B - no root capabilities: 

&lt;ul&gt;
&lt;li&gt;Using &lt;code&gt;containers/buildah&lt;/code&gt; in a secure namespace&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;google/go-container-registry&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a id="contexta"&gt;&lt;/a&gt; Context A - having root capabilities: Using &lt;code&gt;containers/buildah&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;For the task of building the graph image, my first idea was to rely on &lt;a href="https://github.com/containers/buildah"&gt;buildah&lt;/a&gt;.&lt;br&gt;
In fact, our design was already heavily relying on &lt;a href="https://github.com/containers/image"&gt;containers/image&lt;/a&gt; for all things regarding copying images from one registry to the other, or from one registry to an archive. The obvious choice was to use the same suite of modules in order to keep dependencies to a minimum. &lt;/p&gt;

&lt;p&gt;My implementation effort was greatly guided by Buildah's tutorial &lt;a href="https://github.com/containers/buildah/blob/main/docs/tutorials/04-include-in-your-build-tool.md"&gt;4-Include in your build tool&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I'm assuming here that the golang binary that I'm building can have root privileges. If this is not your context, and you'd like to run this binary as non-root, you will need a special setup of the &lt;code&gt;builder&lt;/code&gt; (which you can find in the next section).&lt;/p&gt;

&lt;p&gt;With the assumption that root privileges are available, the implementation is fairly simple. As you'll see below, each instruction of the Containerfile has an equivalent method in the &lt;code&gt;builder&lt;/code&gt; interface.&lt;/p&gt;

&lt;p&gt;I encountered one small gotcha: Any files or folders that you want to copy/add to the image need to be in the current working directory. &lt;/p&gt;

&lt;p&gt;For our development, this was a little incovenience: why would someone using the tool in his home directory suddenly end up with Openshift's upgrade graph metadata poluting his home?! But this could easily be worked around by cleaning up in a &lt;code&gt;defer&lt;/code&gt; statement when the &lt;code&gt;builder&lt;/code&gt; was done (regardless of the build outcome: success or failure).&lt;/p&gt;

&lt;p&gt;All the code is available &lt;a href="https://github.com/sherine-k/graph-data-image-builder/tree/main"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Now let's break down what needs to be done:&lt;/p&gt;
&lt;h4&gt;
  
  
  Initializing the builder - FROM instruction
&lt;/h4&gt;

&lt;p&gt;I want to initialize the &lt;code&gt;builder&lt;/code&gt; on ubi9 image. This is passed in the &lt;code&gt;BuilderOptions&lt;/code&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const(
    graphBaseImage              string = "registry.access.redhat.com/ubi9/ubi:latest"
)
// ... truncated code
builderOpts := buildah.BuilderOptions{
  FromImage:    graphBaseImage,
  Capabilities: capabilitiesForRoot,
  Logger:       logger,
}
builder, err := buildah.NewBuilder(context.TODO(), buildStore, builderOpts)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Adding a layer - ADD instruction
&lt;/h4&gt;

&lt;p&gt;Given that I have prepared the files that need to be copied to the image in &lt;code&gt;graphDataUntarFolder&lt;/code&gt;, I can add the content of the whole folder using &lt;code&gt;builder.Add&lt;/code&gt;. The &lt;code&gt;AddAndCopyOptions&lt;/code&gt; can help set the userID and groupID owning these files and folders inside the container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    addOptions := buildah.AddAndCopyOptions{Chown: "0:0", PreserveOwnership: false}
    addErr := builder.Add(graphDataDir, false, addOptions, graphDataUntarFolder)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Updating the command - CMD instruction
&lt;/h4&gt;

&lt;p&gt;Next, we want to setup the command of the container image. This is rather straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    builder.SetCmd([]string{"/bin/bash", "-c", fmt.Sprintf("exec cp -rp %s/* %s", graphDataDir, graphDataMountPath)})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Building and pushing
&lt;/h4&gt;

&lt;p&gt;It's now time to build the image and push it. By default, you can push to the store by first preparing the image reference like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;imageRef, err := is.Transport.ParseStoreReference(buildStore, "docker.io/myusername/my-image")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But in my case, I opted for pushing it directly to the destination registry, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    imageRef, err := alltransports.ParseImageName("docker://localhost:7000/" + graphImageName)
  // ... truncated code
    imageId, _, _, err := builder.Commit(context.TODO(), imageRef, buildah.CommitOptions{})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;a id="contextb1"&gt;&lt;/a&gt;Context B - using &lt;code&gt;buildah&lt;/code&gt; as non root
&lt;/h3&gt;

&lt;p&gt;oc-mirror being a CLI plugin, it should not require any extra root permissions in order to build images. &lt;/p&gt;

&lt;p&gt;Buildah provides a way to run as non-root. But before we delve into that, a small parenthesis on the configuration of the store that Buildah uses:&lt;/p&gt;

&lt;h4&gt;
  
  
  Store defaults
&lt;/h4&gt;

&lt;p&gt;Buildah relies on a build store for keeping track of layers, images pulled, built, etc. For setting up the build store, I simply used all the default setups available in the &lt;code&gt;buildah&lt;/code&gt; module, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    logger := logrus.New()
    logger.Level = logrus.DebugLevel
    buildStoreOptions, err := storage.DefaultStoreOptionsAutoDetectUID()
  // ... truncated code
    conf, err := config.Default()
  // ... truncated code
    capabilitiesForRoot, err := conf.Capabilities("root", nil, nil)
  // ... truncated code
    buildStore, err := storage.GetStore(buildStoreOptions)
    // ... truncated code
    defer buildStore.Shutdown(false)
    builderOpts := buildah.BuilderOptions{
        FromImage:    graphBaseImage,
        Capabilities: capabilitiesForRoot,
        Logger:       logger,
    }
    builder, err := buildah.NewBuilder(context.TODO(), buildStore, builderOpts)
  // ... truncated code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Setup for non-root execution
&lt;/h4&gt;

&lt;p&gt;In order to integrate the &lt;code&gt;buildah&lt;/code&gt; module to your golang product without root privileges, buildah's recommendation is to pause the execution of the go binary, create a user namespace where it could be root, and re-execute the binary in that user namespace.&lt;/p&gt;

&lt;p&gt;This is achieved by adding the following lines in &lt;code&gt;main.go&lt;/code&gt;, as early as you can in the &lt;code&gt;main&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if buildah.InitReexec() {
  return
}
unshare.MaybeReexecUsingUserNamespace(false)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This has to be added in the &lt;code&gt;main&lt;/code&gt; function: you have to keep in mind that the execution will restart from the beginning, so any initializations will be done a second time. &lt;/p&gt;

&lt;h5&gt;
  
  
  Impacts on debugging
&lt;/h5&gt;

&lt;p&gt;Re-executing has a few impacts on the way we debug our code: &lt;br&gt;
This modifies the debugging process: In order to debug, I had to launch dlv debugger in a user namespace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;podman unshare dlv debug --headless --listen=:43987 main.go 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PS: if you need to pass arguments to &lt;code&gt;main&lt;/code&gt;, you can add &lt;code&gt;--&lt;/code&gt; to the command above, then append any arguments you have.&lt;/p&gt;

&lt;p&gt;Once the command above is triggered, it is possible to use delve to debug (either using dlv directly or attaching to it with a client).&lt;/p&gt;

&lt;p&gt;If you use VSCode, it is possible to attach it to the dlv process running in the background. This is achieved by adding the following code to the configurations[] inside of the launch.json:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "name": "Attach Package",
    "type": "go",
    "debugAdapter": "dlv-dap",
    "request": "attach",
    "mode": "remote",
    "host": "localhost",
    "port": 43987,
},
{
    "name": "Attach Tests",
    "type": "go",
    "debugAdapter": "dlv-dap",
    "request": "attach",
    "mode": "remote",
    "host": "localhost",
    "port": 43987,
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Impacts on users
&lt;/h5&gt;

&lt;p&gt;Finally, for the use cases where our binary must run in a container, or in a pod on a Kubernetes cluster, it is important to setup securityContext and to list all the capabilities necessary to be able to run the binary inside the container. Among these capabilities, you need to include &lt;code&gt;CAP_SETGID&lt;/code&gt; and &lt;code&gt;CAP_SETUID&lt;/code&gt;. Other capabilities might as well be needed. &lt;/p&gt;

&lt;h4&gt;
  
  
  Full code
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://github.com/sherine-k/graph-data-image-builder/tree/main"&gt;graph-data-image-builder&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a id="contextb2"&gt;&lt;/a&gt;Context B - Using &lt;code&gt;go-containerregistry&lt;/code&gt; as non-root
&lt;/h3&gt;

&lt;p&gt;I also explored another module, &lt;a href="https://github.com/google/go-containerregistry"&gt;go-containerregistry&lt;/a&gt;, in order to build images without root privileges. The approach is completely different, and we can manipulate each component of the container image separately. This can present an advantage, if you're looking for a way to fine tune things.&lt;/p&gt;

&lt;h4&gt;
  
  
  Preparing for use of &lt;code&gt;go-container-registry&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;In order to start using the &lt;code&gt;remote&lt;/code&gt; package of &lt;code&gt;go-container-registry&lt;/code&gt; to pull/push images, you need to set :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;nameOptions&lt;/code&gt;: &lt;code&gt;StrictValidation&lt;/code&gt; vs &lt;code&gt;WeakValidation&lt;/code&gt;, and the possibility for default registries to be used while referring to container images&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;remoteOptions&lt;/code&gt;: which group all configurations related to pulling and pushing images, such as:

&lt;ul&gt;
&lt;li&gt;connection proxies, timeouts, keepAlives, use of http2 or http1.1&lt;/li&gt;
&lt;li&gt;configuration files containing credentials for registries &lt;/li&gt;
&lt;li&gt;TLS verification explicit disabling (if needed)
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    nameOptions := []name.Option{
        name.StrictValidation,
    }
    remoteOptions := []remote.Option{
        remote.WithAuthFromKeychain(authn.DefaultKeychain), // this will try to find .docker/config first, $XDG_RUNTIME_DIR/containers/auth.json second
        remote.WithContext(context.TODO()),
        // doesn't seem possible to use registries.conf here.
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Pulling the origin image
&lt;/h4&gt;

&lt;p&gt;Each image we want to build needs to be copied to a folder of your choosing on local disk. That folder (&lt;code&gt;layoutDir&lt;/code&gt;) will contain the image layout, with any manifest-list, oci index, manifest, config, and layers...&lt;/p&gt;

&lt;p&gt;This is achieved by using &lt;a href="https://github.com/google/go-containerregistry/pkg/v1/remote"&gt;&lt;code&gt;remote&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/google/go-containerregistry/pkg/v1/layout"&gt;&lt;code&gt;layout&lt;/code&gt;&lt;/a&gt; like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  imgRef := "registry.access.redhat.com/ubi9/ubi:latest"
    ref, err := name.ParseReference(imgRef, b.NameOpts...)
    if err != nil {
        return "nil", err
    }
    idx, err := remote.Index(ref, b.RemoteOpts...)
    if err != nil {
        return "", err
    }
    layoutPath:= layout.Write(layoutDir, idx)
    return layoutPath
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Creating a layer
&lt;/h4&gt;

&lt;p&gt;Adding a layer from a tar can be achieved very easily using &lt;a href="https://github.com/google/go-containerregistry/pkg/v1/tarball"&gt;&lt;code&gt;tarball&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Given that outputFile is a string containing the path to a tar file, &lt;code&gt;LayerFromFile&lt;/code&gt; automatically untars the tar file contents and constructs a layer from that.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;outputFile&lt;/code&gt; could be anywhere on the filesystem. There are no restrictions to it being saved to the working directory like in &lt;code&gt;buildah&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  layerOptions := []tarball.LayerOption{}
  layer, err := tarball.LayerFromFile(outputFile, layerOptions...)
  if err != nil {
    return nil, err
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Updating the command
&lt;/h4&gt;

&lt;p&gt;For changing anything inside an image, &lt;a href="https://github.com/google/go-containerregistry/pkg/v1/mutate"&gt;&lt;code&gt;mutate&lt;/code&gt;&lt;/a&gt; is needed.&lt;/p&gt;

&lt;p&gt;This is slightly more complicated than what this snippet shows, due to the fact that an image might be a dockerv2-2 manifest list or oci index, itself containing several manifests (image for a specific architecture and OS). &lt;/p&gt;

&lt;p&gt;In order to modify the command for the multi-arch image, we'd need to update the config of each of the underlying manifests.&lt;/p&gt;

&lt;p&gt;But let's keep that out for now, and focus on how to modify the command for a single manifest. The full code is &lt;a href="https://github.com/openshift/oc-mirror/blob/1c8f538897c88011c51ab53ea5073547521f0676/v2/pkg/imagebuilder/builder.go#L114"&gt;here&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// layoutPath is the result of layout.Write from the previous snippet
idx, err := layoutPath.ImageIndex()
if err != nil {
    return err
}
idxManifest, err := idx.IndexManifest()
if err != nil {
    return err
}
manifest := idxManifest.Manifests[0]
currentHash := *manifest.Digest.DeepCopy()
img, err := idx.Image(currentHash)
cfg, err := img.ConfigFile()
if err != nil {
  return nil, err
}
cfg.Config.Cmd = cmd
img, err = mutate.Config(img, cfg.Config)
if err != nil {
  return nil, err
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Building and pushing
&lt;/h4&gt;

&lt;h5&gt;
  
  
  Adding the layer
&lt;/h5&gt;

&lt;p&gt;Same as for the modification of the command, adding a layer is achieved with &lt;a href="https://github.com/google/go-containerregistry/pkg/v1/mutate"&gt;&lt;code&gt;mutate&lt;/code&gt;&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// `img` is the single arch image from the index. We get it by calling `idx.Image(currentHash)` like in the previous snippet
// `layer` is the 
additions := make([]mutate.Addendum, 0, len(layers))
for _, layer := range layers {
  additions = append(additions, mutate.Addendum{Layer: layer, MediaType: mt})
}
img, err = mutate.Append(img, additions...)
if err != nil {
  return nil, err
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Building new manifests and index
&lt;/h5&gt;

&lt;p&gt;Once a layer is added, or a Config modified, the manifest of the image should be updated. To be more exact, we need to remove the old manifest from the index, and add a new one.&lt;/p&gt;

&lt;p&gt;This is done by creating a new descriptor for the &lt;code&gt;img&lt;/code&gt; that was updated in previous snippets&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;desc, err := partial.Descriptor(img)
if err != nil {
    return nil, err
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need to update the image index, by replacing the descriptor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;add := mutate.IndexAddendum{
    Add:        img,
    Descriptor: *desc,
}
modifiedIndex := mutate.AppendManifests(mutate.RemoveManifests(idx, match.Digests(currentHash)), add)
resultIdx = modifiedIndex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Full code
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/openshift/oc-mirror/blob/1c8f538897c88011c51ab53ea5073547521f0676/v2/pkg/release/graph.go#L17"&gt;https://github.com/openshift/oc-mirror/blob/1c8f538897c88011c51ab53ea5073547521f0676/v2/pkg/release/graph.go#L17&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/openshift/oc-mirror/blob/1c8f538897c88011c51ab53ea5073547521f0676/v2/pkg/imagebuilder/builder.go#L114"&gt;https://github.com/openshift/oc-mirror/blob/1c8f538897c88011c51ab53ea5073547521f0676/v2/pkg/imagebuilder/builder.go#L114&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Using &lt;code&gt;buildah&lt;/code&gt; is much more simple: out of the box, it has support for multi-arch image building, as well as support for &lt;a href="https://github.com/containers/image/blob/main/docs/containers-registries.conf.5.md"&gt;registries.conf&lt;/a&gt;, which was a requirement for our product.&lt;/p&gt;

&lt;p&gt;Furthermore, and like shown in this blog entry, each Containerfile instruction maps to a builder method. This makes the builder very easy to use.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;go-containerregistry&lt;/code&gt; has all the necessary interfaces and methods to manipulate all the building blocks of container images, regardless of their format (dockerv2-1, dockerv2-2 or oci). It is probably worth investigating whether another golang module builds on top of &lt;code&gt;go-containerregistry&lt;/code&gt; and provides an experience closer to that of a &lt;code&gt;builder&lt;/code&gt;, abstracting away all the lower level changes, and allowing for building multi-arch images easily. But that's a subject for a next blog...&lt;/p&gt;

</description>
      <category>containers</category>
      <category>go</category>
      <category>docker</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
