<?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>DEV Community: Maciej Mionskowski</title>
    <description>The latest articles on DEV Community by Maciej Mionskowski (@maciekmm).</description>
    <link>https://dev.to/maciekmm</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%2F840951%2F7b30bf25-e893-4d72-b914-5e26042ff82b.jpeg</url>
      <title>DEV Community: Maciej Mionskowski</title>
      <link>https://dev.to/maciekmm</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/maciekmm"/>
    <language>en</language>
    <item>
      <title>How to pass environment variables to front-end container images</title>
      <dc:creator>Maciej Mionskowski</dc:creator>
      <pubDate>Sun, 10 Apr 2022 16:19:07 +0000</pubDate>
      <link>https://dev.to/maciekmm/how-to-pass-environment-variables-to-front-end-container-images-3ihi</link>
      <guid>https://dev.to/maciekmm/how-to-pass-environment-variables-to-front-end-container-images-3ihi</guid>
      <description>&lt;p&gt;Environment variables are a standard way to parametrize backend containers. For some reason, they haven't seen wide adoption on the frontend side, which just as much requires customization. Both &lt;em&gt;React&lt;/em&gt; and &lt;em&gt;Vue&lt;/em&gt; still recommend creating separate &lt;code&gt;.env&lt;/code&gt; files for different environments, which is unwieldy at best if you want to containarize the application. In this tutorial, I will guide You through an opinionated way to &lt;strong&gt;create environment agnostic frontend images&lt;/strong&gt; in React.&lt;/p&gt;

&lt;h1&gt;
  
  
  What are the advantages of environment agnostic frontend images?
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Reduced CI pipeline time - single build pass means no need to create three different images for your development, staging, and production environments&lt;/li&gt;
&lt;li&gt;Simplified environment promotion - deploy an image to staging environment and promote it to production once all tests pass&lt;/li&gt;
&lt;li&gt;Mitigated risk of deploying improper image to production environment&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  How to add an API URL environment variable to frontend Docker images?
&lt;/h1&gt;

&lt;p&gt;The most common use case for environment variables on the frontend side is to have a customizable backend url for dev, staging and production environments respectively.&lt;br&gt;
This example is based on a &lt;strong&gt;React&lt;/strong&gt; app created using create-react-app. But the examples can be easily ported to &lt;em&gt;Vue&lt;/em&gt; or even &lt;em&gt;Next&lt;/em&gt; with slight modifications.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: Create &lt;code&gt;/public/env.js&lt;/code&gt; file
&lt;/h2&gt;

&lt;p&gt;You should put values related to the local development environment there. You might decide to commit the file to the code repository assuming that all local environments will have the same configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;API_HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:10001&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// local development API_HOST if applicable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Create a &lt;code&gt;script&lt;/code&gt; tag in &lt;code&gt;index.html&lt;/code&gt;'s &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; section pointing to the file created previously.
&lt;/h2&gt;

&lt;p&gt;It is important to load the file before loading any other javascript that will use the variables, thus &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; seems to be a good place.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    ...
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"%PUBLIC_URL%/env.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Create a &lt;code&gt;docker&lt;/code&gt; directory
&lt;/h2&gt;

&lt;p&gt;This is where all image related files will live to reduce clutter in the project root.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Create &lt;code&gt;50-substitute-env-variables.sh&lt;/code&gt; under &lt;code&gt;/docker&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;50-substitute-env-variables.sh&lt;/code&gt; script will be responsible for substituting environment variables in container &lt;strong&gt;runtime&lt;/strong&gt;. It will utilize a built-in feature in the nginx image that runs scripts from &lt;code&gt;/docker-entrypoint.d/&lt;/code&gt; directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env sh&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; errexit
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; nounset 
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; pipefail

: &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;API_HOST&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="c"&gt;# ensure API_HOST exists and exit otherwise&lt;/span&gt;

&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt; &amp;gt; /usr/share/nginx/html/env.js
window.env = {};
window.env.API_HOST = "&lt;/span&gt;&lt;span class="nv"&gt;$API_HOST&lt;/span&gt;&lt;span class="sh"&gt;";
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't forget to make it executable by running &lt;code&gt;chown +x 50-substitute-env-variables.sh&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Create &lt;code&gt;nginx.conf&lt;/code&gt; under &lt;code&gt;/docker&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;You might want to tweak the &lt;code&gt;try_files&lt;/code&gt; directive based on the router you use. The configuration below will try to load a file if it exists and the &lt;code&gt;index.html&lt;/code&gt; otherwise.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;worker_processes&lt;/span&gt;    &lt;span class="s"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;events&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;worker_connections&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;http&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;server_tokens&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;listen&lt;/span&gt;  &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;root&lt;/span&gt;    &lt;span class="n"&gt;/usr/share/nginx/html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="n"&gt;/etc/nginx/mime.types&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="s"&gt;index.html&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 6: Create a &lt;code&gt;Dockerfile&lt;/code&gt; under &lt;code&gt;/docker&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;We will use multi-stage Docker image to reduce the image size. Note that You should bind both &lt;code&gt;node&lt;/code&gt; and &lt;code&gt;nginx&lt;/code&gt; images to some version.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:current as build&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /src&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json /src&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . /src&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build


&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; nginx:alpine&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /usr/share/nginx/html/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /src/build /usr/share/nginx/html/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; /docker/nginx.conf /etc/nginx/nginx.conf&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; /docker/50-substitute-env-variables.sh /docker-entrypoint.d/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the end of this step the directory structure should look as follows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/app
    /docker
        50-substitute-env-variables.sh
        Dockerfile
        nginx.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 7: Reference the environment variable in code
&lt;/h2&gt;

&lt;p&gt;You can reference the &lt;code&gt;API_HOST&lt;/code&gt; variable under &lt;code&gt;window.env.API_HOST&lt;/code&gt;, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiHost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;API_HOST&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;App&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;API&lt;/span&gt; &lt;span class="nx"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;apiHost&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 8: Build the image
&lt;/h2&gt;

&lt;p&gt;From app's root directory execute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-f&lt;/span&gt; docker/Dockerfile &lt;span class="nt"&gt;-t&lt;/span&gt; docker.your-company.com/app:version &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After successful build, you can start the container by typing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;API_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://prod.company.com/ &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 docker.your-company.com/app:version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In case you forget to specify the environment variable the container will exit with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/docker-entrypoint.d/50-substitute-env-variables.sh: line 7: API_HOST: parameter not set
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now access the container under 127.0.0.1:8080.&lt;/p&gt;

&lt;p&gt;The full code is available on &lt;a href="https://github.com/maciekmm/environment-agnostic-frontend-docker-image"&gt;Github&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>docker</category>
      <category>webdev</category>
      <category>react</category>
    </item>
    <item>
      <title>The journey of sharing a wired USB printer over the network</title>
      <dc:creator>Maciej Mionskowski</dc:creator>
      <pubDate>Mon, 04 Apr 2022 17:17:03 +0000</pubDate>
      <link>https://dev.to/maciekmm/the-journey-of-sharing-a-wired-usb-printer-over-the-network-fg8</link>
      <guid>https://dev.to/maciekmm/the-journey-of-sharing-a-wired-usb-printer-over-the-network-fg8</guid>
      <description>&lt;p&gt;I was in the market for a printer that was cheap to buy and cheap to run. I did not print in color, so I concluded that a &lt;del&gt;dot matrix&lt;/del&gt; laser printer would be a good choice.&lt;br&gt;
I looked up a couple of units and decided on Brother DCP-1510 as it was on sale for ~$100 with replacement toners running for $8 apiece. Not a bad deal. It had one caveat - no ethernet port, no WiFi support, and no &lt;a href="https://en.wikipedia.org/wiki/Internet_Printing_Protocol"&gt;Internet Printing Protocol&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WY95YbLo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://imgs.xkcd.com/comics/all_in_one.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WY95YbLo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://imgs.xkcd.com/comics/all_in_one.png%23center" alt="All-In-One" width="532" height="414"&gt;&lt;/a&gt;&lt;br&gt;
I did not need all the features, but an ethernet port would be nice&lt;br&gt;
&lt;a href="https://xkcd.com/2369/"&gt;Courtesy of XKCD&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That did not put me off. I have never been printing much and could live with the cable. I also knew I could attach it to a print server such as &lt;a href="http://www.cups.org/"&gt;CUPS&lt;/a&gt; and add networking capabilities to the network-impaired printer. I had a Raspberry Pi Zero sitting around, and I wouldn't hesitate to use it. This post is a story about the journey I experienced setting it up.&lt;/p&gt;
&lt;h2&gt;
  
  
  The driver support or lack thereof
&lt;/h2&gt;

&lt;p&gt;I flashed &lt;a href="http://archlinuxarm.org/"&gt;Arch Linux ARM&lt;/a&gt; onto my RPi and went driver hunting. It turned out the manufacturer does not support the ARM architecture.&lt;/p&gt;

&lt;p&gt;After a couple of &lt;a href="https://ddg.gg"&gt;DDG&lt;/a&gt; searches, I found &lt;a href="https://github.com/pdewacht/brlaser"&gt;brlaser&lt;/a&gt; - a community-driven Brother driver. &lt;br&gt;
Perfect! I installed CUPS, compiled the driver, and shared the printer over the network.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kxKvdZHx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ir5xqysvqscezqpsi3zk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kxKvdZHx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ir5xqysvqscezqpsi3zk.png" alt="cups driver selection" width="408" height="265"&gt;&lt;/a&gt;&lt;br&gt;
Brlaser as seen in the driver selection prompt.&lt;/p&gt;

&lt;p&gt;I clicked the &lt;em&gt;Print&lt;/em&gt; button.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why does it take 45 seconds to print a single page‽
&lt;/h2&gt;

&lt;p&gt;My eyes turned towards the printer anticipating the first sheet of paper to appear quickly, but nothing happened. Not until I tried to ssh into the Pi and started debugging. After a couple of seconds I began to hear the &lt;em&gt;brrr&lt;/em&gt; printer noise while it was spitting out the page. I tried to print a second one as I thought it needed some priming, but it also took well over half a minute.&lt;/p&gt;

&lt;p&gt;I clicked &lt;em&gt;Print&lt;/em&gt; again and began observing the CUPS interface. It took 30 s to get through the &lt;em&gt;Processing Page&lt;/em&gt; state. Something was off.&lt;/p&gt;

&lt;p&gt;As it turns out, Raspberry Pi Zero is not that powerful. The driver uses &lt;code&gt;ghostscript&lt;/code&gt; which pegged the CPU usage to 100%. Back to the drawing board.&lt;/p&gt;
&lt;h2&gt;
  
  
  Can I share an arbitrary USB device over the network? Sure I can!
&lt;/h2&gt;

&lt;p&gt;I wasn't aware of the &lt;em&gt;RAW&lt;/em&gt; queue mode in CUPS at the time, but I heard about &lt;a href="http://usbip.sourceforge.net/"&gt;usbip&lt;/a&gt;, which sounded like a compelling solution to the problem, or so I thought.&lt;/p&gt;

&lt;p&gt;I followed a great &lt;a href="https://wiki.archlinux.org/title/USB/IP"&gt;Arch Wiki tutorial on usbip&lt;/a&gt; and sure enough, I had my printer attached.&lt;/p&gt;

&lt;p&gt;The need to load a kernel module was a bit unsettling, but I installed the drivers and configured the printer using CUPS successfully.&lt;/p&gt;

&lt;p&gt;I hit &lt;em&gt;Print&lt;/em&gt; and almost immediately had the page handy. Whoa, that was easier than expected!&lt;/p&gt;
&lt;h3&gt;
  
  
  It's not all roses 🌹
&lt;/h3&gt;

&lt;p&gt;I rebooted my laptop the next day and tried to print again, a real document this time.&lt;/p&gt;

&lt;p&gt;It wouldn't work - the device was gone - I couldn't detach the device, I couldn't attach a new one, I tried reloading the kernel module, and it wouldn't work either, nada.&lt;/p&gt;

&lt;p&gt;At that point, I concluded I'm not in favor of running a kernel module that's misbehaving &lt;br&gt;
I also had some security concerns around the whole architecture and therefore I uninstalled it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Learning about RAW queues
&lt;/h2&gt;

&lt;p&gt;I came back to the problem after a couple of days, but now I had a powerful tool under my belt: knowledge about CUPS RAW queues.&lt;/p&gt;

&lt;p&gt;A RAW queue is&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[...] a queue where the filtering system is not involved and the print job goes directly to a printer or another queue: &lt;br&gt;
&lt;a href="https://wiki.debian.org/CUPSPrintQueues"&gt;https://wiki.debian.org/CUPSPrintQueues&lt;/a&gt;"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This seemed promising for my use case. The idea was to set up a RAW queue on the Pi and then do the heavy lifting (filtering) on significantly more powerful user machines.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.----------------.         .--------------.        .---------.
|       PC       |         | Raspberry PI |        .         .
|   (filtering)  +--------&amp;gt;|  (raw queue) +-------&amp;gt;| Printer |
| (brlaser + gs) |         |(no filtering)|        '         .
'----------------'         '--------------'        '---------'

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

&lt;/div&gt;



&lt;p&gt;I quickly compiled this setup and it worked out perfectly.&lt;/p&gt;

&lt;p&gt;Note: CUPS plans to deprecate drivers and raw queues in the future because of how wide-spread IPP has become. I don't consider this to be a big issue, you can always pin the CUPS version. There's still a ton of old hardware out there, and that won't change quickly (and doesn't have to).&lt;/p&gt;

&lt;h2&gt;
  
  
  The SD card gives up
&lt;/h2&gt;

&lt;p&gt;After a few weeks, the SD card running the CUPS server gave up. &lt;br&gt;
I bought a new one and tried to quickly reinstall everything, but &lt;a href="https://archlinuxarm.org/forum/viewtopic.php?f=3&amp;amp;t=15721"&gt;Arch Linux ARM abandoned armv6 architecture&lt;/a&gt; in the meantime. I decided to use &lt;a href="https://www.raspberrypi.com/software/operating-systems/"&gt;Raspberry Pi OS&lt;/a&gt; and automate the setup.&lt;/p&gt;
&lt;h2&gt;
  
  
  Automating the build process
&lt;/h2&gt;

&lt;p&gt;I like to have my infrastructure defined in code and I maintain a number of Ansible playbooks and Terraform workspaces to control my servers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://packer.io"&gt;Packer&lt;/a&gt; seemed like the perfect tool for the job.&lt;br&gt;
I have never used it before and wanted to get familiar with the tool.&lt;br&gt;
It doesn't come with ARM support out of the box, but &lt;a href="https://www.packer.io/docs/builders/community-supported"&gt;there are two community projects to fill that niche&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I tried the &lt;a href="https://github.com/mkaczanowski/packer-builder-arm/"&gt;packer-builder-arm&lt;/a&gt; first, but it couldn't run my Ansible playbook due to &lt;a href="https://github.com/mkaczanowski/packer-builder-arm/issues/169"&gt;a bug&lt;/a&gt;. I applied a patch but quickly ran into other issues.&lt;/p&gt;

&lt;p&gt;At that point, I decided to use &lt;a href="https://github.com/solo-io/packer-plugin-arm-image"&gt;packer-plugin-arm-image&lt;/a&gt; instead. The setup did not work out of the box, but after a &lt;a href="https://github.com/solo-io/packer-plugin-arm-image/pull/132"&gt;simple PR&lt;/a&gt;, it built my first empty image and proved it's possible to build an ARM image locally.&lt;/p&gt;
&lt;h3&gt;
  
  
  Defining goals
&lt;/h3&gt;

&lt;p&gt;I wanted an end-to-end setup with the following functionality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OS installation (Raspberry Pi OS)&lt;/li&gt;
&lt;li&gt;WiFi setup&lt;/li&gt;
&lt;li&gt;Autodiscovery via mDNS &amp;amp; DNSSD&lt;/li&gt;
&lt;li&gt;CUPS installation&lt;/li&gt;
&lt;li&gt;Printer configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I developed two Ansible playbooks to accomplish that. I will skip the technicalities and maybe write about the details another time.&lt;/p&gt;
&lt;h2&gt;
  
  
  Run it yourself!
&lt;/h2&gt;

&lt;p&gt;The final solution with a comprehensive README can be found under the &lt;a href="https://github.com/maciekmm/printer-rpi-image"&gt;maciekmm/printer-rpi-image github repo&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Building, flashing, and running the image yourself is as simple as running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# build the base image&lt;/span&gt;
&lt;span class="nv"&gt;WIFI_SSID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;SSID&amp;gt; &lt;span class="nv"&gt;WIFI_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;PASSWORD&amp;gt; &lt;span class="nv"&gt;SSH_PUBLIC_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; ~/.ssh/id_ed25519.pub&lt;span class="si"&gt;)&lt;/span&gt; vagrant up
&lt;span class="c"&gt;# flash the image onto the sd card&lt;/span&gt;
&lt;span class="nb"&gt;dd &lt;/span&gt;&lt;span class="nv"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4M &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./output-raspberry_pi_os.img &lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;sdcarddevice&amp;gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sync&lt;/span&gt;
&lt;span class="c"&gt;# NOTE: this runs on the live Pi. Connect it first with printers attached!&lt;/span&gt;
&lt;span class="c"&gt;# configure the printer and firewalls&lt;/span&gt;
ansible-playbook &lt;span class="nt"&gt;-i&lt;/span&gt; hosts live.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's left is configuring the driver and discovering the printer locally. This can be done by installing CUPS locally and running through the wizard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;I accomplished several things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I made my wired printer wireless,&lt;/li&gt;
&lt;li&gt;I learned how to use Packer,&lt;/li&gt;
&lt;li&gt;I learned a bit about CUPS queues,&lt;/li&gt;
&lt;li&gt;I published &lt;a href="https://github.com/maciekmm/printer-rpi-image"&gt;an open source project&lt;/a&gt; for you to be able to do the same.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Overall, this was an interesting project and I'm happy to share this story.&lt;/p&gt;

</description>
      <category>raspberrypi</category>
      <category>linux</category>
      <category>cups</category>
      <category>packer</category>
    </item>
    <item>
      <title>How to detect breaking changes and lint Protobuf automatically using Gitlab CI and Buf</title>
      <dc:creator>Maciej Mionskowski</dc:creator>
      <pubDate>Sat, 02 Apr 2022 10:33:54 +0000</pubDate>
      <link>https://dev.to/maciekmm/how-to-detect-breaking-changes-and-lint-protobuf-automatically-using-gitlab-ci-and-buf-33k2</link>
      <guid>https://dev.to/maciekmm/how-to-detect-breaking-changes-and-lint-protobuf-automatically-using-gitlab-ci-and-buf-33k2</guid>
      <description>&lt;p&gt;&lt;strong&gt;Protocol Buffers&lt;/strong&gt; or &lt;strong&gt;Protobufs&lt;/strong&gt; are language-agnostic &lt;br&gt;
mechanisms for serializing data. &lt;br&gt;
Protobuf schemas are specified using &lt;strong&gt;Protocol Buffer language&lt;/strong&gt;, which is among the most popular and widely adopted &lt;a href="https://en.wikipedia.org/wiki/Interface_description_language"&gt;IDL&lt;/a&gt;s in the industry.&lt;/p&gt;

&lt;p&gt;Protobufs are most commonly used in &lt;a href="https://grpc.io"&gt;RPC&lt;/a&gt; services for inter-service communication. Their usage is also growing for public-facing interfaces, and recently they have been adopted in tools such as &lt;a href="https://docs.confluent.io/platform/current/schema-registry/serdes-develop/serdes-protobuf.html"&gt;Apache Kafka&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developers.google.com/protocol-buffers/docs/proto3"&gt;Used correctly&lt;/a&gt;, they enable both forward and backward compatible message producing and consuming. Meaning that old consumers/clients  (using old Protobuf schema) can consume messages from new producers/servers and vice-versa. For me, this is the most compelling point in the offerings of Protobufs.&lt;/p&gt;
&lt;h1&gt;
  
  
  Breaking changes detection
&lt;/h1&gt;

&lt;p&gt;To maintain backward/forward compatibility in Protobufs, every change to the schema must be thoroughly code-reviewed and tested for compliance. Humans are prone to making errors. Therefore, several tools have emerged to help with the process, most notably &lt;a href="https://buf.build"&gt;Buf&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We’re working quickly to build a modern Protobuf ecosystem. Our first tool is the Buf CLI, built to help you create consistent Protobuf APIs that preserve compatibility and comply with design best practices. The tool is currently available on an open-source basis.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To check if the current working copy is compatible with a previous revision, you can invoke.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;buf check &lt;span class="nt"&gt;--against&lt;/span&gt; &lt;span class="s1"&gt;'reference-to-a-previous-revision'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where &lt;code&gt;reference-to-a-previous-revision&lt;/code&gt; may be either a git repository reference or an &lt;a href="https://docs.buf.build/tour-7"&gt;image built&lt;/a&gt; by Buf.&lt;/p&gt;

&lt;p&gt;To check against a particular branch, you can execute&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;buf check &lt;span class="nt"&gt;--against&lt;/span&gt; &lt;span class="s1"&gt;'.git#branch=master'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For all other means of referencing particular code revision, you can consult Buf's &lt;a href="https://docs.buf.build/breaking-usage#compare-directly-against-a-git-branch-or-git-tag"&gt;excellent docs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ensuring adequate compatibility constraints
&lt;/h2&gt;

&lt;p&gt;Not all systems use Protobufs equal, some will serialize the message to &lt;code&gt;JSON&lt;/code&gt; down the line, others will solely rely on binary messages. Buf is flexible in terms of defining an adequate compatibility level for a project.&lt;/p&gt;

&lt;p&gt;Buf's docs provide a great &lt;a href="https://docs.buf.build/breaking-overview"&gt;overview&lt;/a&gt; of supported rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detecting breaking changes using Gitlab CI
&lt;/h2&gt;

&lt;p&gt;When using a Merge Request based flow it is usually enough to check every merge request for compatibility breaking changes against the target branch. &lt;/p&gt;

&lt;p&gt;The solution does not cover all situations, most notably when changes are introduced without using merge requests, but supporting all cases would require building and storing buf images. &lt;/p&gt;

&lt;p&gt;Besides a static binary, Buf also provides a docker image. Using it will simplify the workflow.&lt;/p&gt;

&lt;p&gt;To check every merge request introduce the following snippet to your repository's &lt;code&gt;.gitlab-ci.yml&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;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;ensure backwards compatibility&lt;/span&gt;

&lt;span class="na"&gt;validate merge request&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;ensure backwards compatibility&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;bufbuild/buf:0.41.0&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;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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$CI_PIPELINE_SOURCE&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;"merge_request_event"'&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;buf breaking --against "${CI_REPOSITORY_URL}#branch=${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;CI_REPOSITORY_URL&lt;/code&gt; expands to a URL that can be supplied to &lt;code&gt;git clone&lt;/code&gt;. It includes a token, therefore, access management is of no concern.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detecting breaking changes using other CI solutions
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/bufbuild/buf-example/"&gt;Buf's repository&lt;/a&gt; contains exemplary workflow definitions for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Travis CI&lt;/li&gt;
&lt;li&gt;Github Actions&lt;/li&gt;
&lt;li&gt;Circle CI&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Code style checking
&lt;/h1&gt;

&lt;p&gt;Linters help to ensure code style consistency across files. With Protobuf it is no different.&lt;/p&gt;

&lt;p&gt;Linting with buf is effortless and can be done by running &lt;code&gt;buf lint&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you encounter errors or warnings from &lt;code&gt;buf&lt;/code&gt;, do not try to fix them absent-mindedly. Things will break if the schema is used in a production environment. Instead, make small, incremental changes and check for incompatibilities. Should one arise, you can &lt;a href="https://docs.buf.build/lint-configuration"&gt;make an exclusion&lt;/a&gt; and fix your mistake in the future interface version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lint automatically using Gitlab CI
&lt;/h2&gt;

&lt;p&gt;To check every commit on every branch, introduce the following snipped to &lt;code&gt;.gitlab-ci.yml&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;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;lint&lt;/span&gt;

&lt;span class="na"&gt;lint&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;lint&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;bufbuild/buf:0.41.0&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;buf lint&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$CI_PIPELINE_SOURCE&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;"merge_request_event"'&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$CI_COMMIT_BRANCH&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;$CI_OPEN_MERGE_REQUESTS'&lt;/span&gt;
      &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;never&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$CI_COMMIT_BRANCH'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rules attached to the lint stage will prevent multiple pipelines running for a merge request.&lt;/p&gt;

&lt;h1&gt;
  
  
  Combining linting with breaking changes detection
&lt;/h1&gt;

&lt;p&gt;You can easily combine examples outlined above in a single &lt;code&gt;.gitlab-ci.yml&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;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;bufbuild/buf:0.41.0&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;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;lint&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ensure backwards compatibility&lt;/span&gt;

&lt;span class="na"&gt;lint&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;lint&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;buf lint&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$CI_PIPELINE_SOURCE&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;"merge_request_event"'&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$CI_COMMIT_BRANCH&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;$CI_OPEN_MERGE_REQUESTS'&lt;/span&gt;
      &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;never&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$CI_COMMIT_BRANCH'&lt;/span&gt;

&lt;span class="na"&gt;validate merge request&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;ensure backwards compatibility&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$CI_PIPELINE_SOURCE&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;"merge_request_event"'&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;buf breaking --against "${CI_REPOSITORY_URL}#branch=${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have created a sample repository under &lt;a href="https://gitlab.com/mionskowski/protobuf-ci"&gt;https://gitlab.com/mionskowski/protobuf-ci&lt;/a&gt;. Navigate to &lt;a href="https://gitlab.com/mionskowski/protobuf-ci/-/merge_requests"&gt;Merge Requests&lt;/a&gt; to see both passed and failed pipelines.&lt;/p&gt;

</description>
      <category>git</category>
      <category>programming</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
