<?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: Julian</title>
    <description>The latest articles on DEV Community by Julian (@serializator).</description>
    <link>https://dev.to/serializator</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%2F145834%2F9da60112-8fb4-484d-84b3-7db27475c3a2.jpg</url>
      <title>DEV Community: Julian</title>
      <link>https://dev.to/serializator</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/serializator"/>
    <language>en</language>
    <item>
      <title>Feedback on Prometheus exporter written in Golang for Magento 2</title>
      <dc:creator>Julian</dc:creator>
      <pubDate>Wed, 20 Oct 2021 20:06:32 +0000</pubDate>
      <link>https://dev.to/serializator/feedback-on-prometheus-exporter-written-in-golang-4mkg</link>
      <guid>https://dev.to/serializator/feedback-on-prometheus-exporter-written-in-golang-4mkg</guid>
      <description>&lt;p&gt;I decided to write 📝 a Prometheus exporter (in Golang) for Magento 2. Why? Because I wanted a better understanding of both Prometheus and Golang. Writing an exporter gives me a practical example for both.&lt;/p&gt;

&lt;p&gt;"Magento 2 Prometheus Exporter" consists of two parts, the Prometheus exporter itself and a module for Magento 2 which extends upon the REST API to expose metrics where the native API of Magento 2 lacks in performance (such as determining the amount of orders).&lt;/p&gt;

&lt;p&gt;I would love (❤️) and appreciate some feedback and constructive criticism on the exporter. Same goes for the Magento 2 module to extend the REST API but, because the personal objective for me is learning more about Prometheus and Golang, feedback on the actual exporter is of much greater value to me.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Serializator/magento2-prometheus-exporter-golang"&gt;https://github.com/Serializator/magento2-prometheus-exporter-golang&lt;/a&gt;&lt;/p&gt;

</description>
      <category>prometheus</category>
      <category>go</category>
      <category>devops</category>
      <category>learning</category>
    </item>
    <item>
      <title>SonarQube Pull Requests in Bitbucket Cloud</title>
      <dc:creator>Julian</dc:creator>
      <pubDate>Sun, 27 Oct 2019 21:52:01 +0000</pubDate>
      <link>https://dev.to/serializator/sonarqube-pull-requests-in-bitbucket-cloud-188b</link>
      <guid>https://dev.to/serializator/sonarqube-pull-requests-in-bitbucket-cloud-188b</guid>
      <description>&lt;h1&gt;
  
  
  Please note ❤️
&lt;/h1&gt;

&lt;p&gt;I created the solution I did and wrote what I wrote because there was a problem I was trying to solve within a set of requirements / "boundaries" lets say.&lt;/p&gt;

&lt;p&gt;Using this solution requires the usage of SonarQube "analysis mode", which is &lt;a href="https://docs.sonarqube.org/7.4/analysis/analysis-parameters/" rel="noopener noreferrer"&gt;deprecated since SonarQube 6.6&lt;/a&gt;. Once this solution is implemented you can't easily upgrade, without breaking the pull request analysis.&lt;/p&gt;

&lt;p&gt;Please use this solution at your own discretion 🔥&lt;/p&gt;




&lt;p&gt;I'll start by saying, the solution was way easier than I originally anticipated... I thought I was gonna have to run a Docker container with a microservice of some kind written in Java, Node.JS or Golang and run Sonar Scanner when a pull request was created or updated based on a webhook from Bitbucket.&lt;/p&gt;

&lt;p&gt;But... As always, eventually the solution was so much easier, but what's the fun in writing a blog when you don't write about the way you failed miserably before succeeding?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it all started, a long... long time ago&lt;/strong&gt;&lt;br&gt;
From the very moment I met her, SonarQube, I knew I was in love. Knowing that it had a tiny, little, cute container that you could easily run with a single keystroke in the command line, with an embedded H2 database for convenience, and even an embedded instance of Elasticsearch, I couldn't be more intrigued 😍&lt;/p&gt;

&lt;p&gt;I looked into some different static analysis tools, such as &lt;a href="https://www.codeclimate.com/" rel="noopener noreferrer"&gt;Code Climate&lt;/a&gt;, &lt;a href="https://www.sonarcloud.io" rel="noopener noreferrer"&gt;SonarCloud&lt;/a&gt; and &lt;a href="https://www.exakat.io" rel="noopener noreferrer"&gt;Exakat&lt;/a&gt;, but they were either priced based on the size of your organization (Code Climate), or your projects (pricing based on LOC for SonarCloud), which might've caused scaling issues in the future. Or they weren't as easy to integrate with the existing tools such as Bitbucket Cloud, or didn't really have a friendly UI for the developer using it in case of Exakat.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Integrating SonarQube with Bitbucket Cloud&lt;/strong&gt; ☁️&lt;br&gt;
&lt;a href="https://www.github.com/mibexsoftware" rel="noopener noreferrer"&gt;Mibex Software&lt;/a&gt; has a wonderful SonarQube plugin, &lt;a href="https://github.com/mibexsoftware/sonar-bitbucket-plugin" rel="noopener noreferrer"&gt;Sonar Bitbucket Cloud Plugin&lt;/a&gt;, which analyzes and comments on your pull requests.&lt;/p&gt;

&lt;p&gt;I decided to give it a go, I cloned the repository, built it with Maven so that I had a beatiful JAR to rsync to my Droplet on DigitalOcean and installed it to my SonarQube instance.&lt;/p&gt;

&lt;p&gt;I installed the &lt;a href="https://marketplace.atlassian.com/apps/1214134/sonar-for-bitbucket-cloud?hosting=cloud&amp;amp;tab=overview" rel="noopener noreferrer"&gt;Sonar for Bitbucket Cloud&lt;/a&gt; plugin through the Bitbucket Marketplace, and expected that it'd have a built-in webhook to inform the SonarQube plugin whenever a pull request was created or updated, ran the analysis and comment on the pull request with its findings.&lt;/p&gt;

&lt;p&gt;But, it wasn't that easy. It turns out, that if I had read the documentation fully, from top to bottom, it documents the fact that I had to run Sonar using Maven, or as I later found out the Sonar Scanner, to trigger an analysis on SonarQube.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It was fun for the time being, but... We'll stay friends!&lt;/strong&gt; ❤️&lt;br&gt;
&lt;a href="https://marketplace.atlassian.com/apps/1214134/sonar-for-bitbucket-cloud?hosting=cloud&amp;amp;tab=overview" rel="noopener noreferrer"&gt;Sonar for Bitbucket Cloud&lt;/a&gt; seemed to really only add the statistics on the overview page of your Bitbucket repository, it didn't really seem to contribute to commenting with found issues on your pull requests.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F9dn1om8a970orxdwjlt7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F9dn1om8a970orxdwjlt7.png" alt="Sonar for Bitbucket Cloud Statistics on Repository Overview"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So... Before running it in automation, lets try it manually?&lt;/strong&gt; 🔧🔨&lt;br&gt;
It turns out that the Sonar Bitbucket Cloud plugin that I installed on my SonarQube installation also works when I simply run the Sonar Scanner with a few more arguments than I'd normally pass to it.&lt;/p&gt;

&lt;p&gt;I hear you thinking, "uhm... but... wasn't that so obvious?!", and I know that now. But in the moment itself I wanted to pull the hairs, that I still have at this age, out of my head because of the frustration. &lt;/p&gt;

&lt;p&gt;Originally I read &lt;a href="https://medium.com/@farooqahmadkhan/decorate-bitbucket-pull-request-with-sonar-qube-comments-81eb6c22f071" rel="noopener noreferrer"&gt;this article (Decorate Bitbucket Cloud Pull Request with SonarQube Server Comments)&lt;/a&gt; to get familiar, so I got confused by the fact that the author was using a Maven goal to run the scanner.&lt;/p&gt;

&lt;p&gt;When I ran the Sonar Scanner manually the pull request was analyzed and the Sonar Bitbucket Cloud plugin successfully commented on it with the issues that were found, in this case none.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

sonar-scanner -Dsonar.projectBaseDir=$(pwd)
    -Dproject.settings=sonar.properties
    -Dsonar.analysis.mode=issues
    -Dsonar.bitbucket.repoSlug=&amp;lt;repoSlug&amp;gt;
    -Dsonar.bitbucket.accountName=&amp;lt;accountName&amp;gt;
    -Dsonar.bitbucket.branchName=&amp;lt;branch&amp;gt;
    -Dsonar.bitbucket.oauthClientKey=&amp;lt;oauth-client-key&amp;gt;
    -Dsonar.bitbucket.oauthClientSecret=&amp;lt;oauth-client-secret&amp;gt;
    -Dsonar.login=&amp;lt;sonar-token&amp;gt;
    -Dsonar.bitbucket.pullRequestId=&amp;lt;pull-request-id&amp;gt;


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

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Feiu26fjxqevy09m835jc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Feiu26fjxqevy09m835jc.png" alt="Bitbucket Pull Request Comments"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should we integrate this with our CI/CD? Of course!&lt;/strong&gt; 🌹&lt;br&gt;
When I knew that the Sonar Scanner worked, I wanted to integrate it with our CI/CD to automate the analysis of both our integration branch as well as pull requests.&lt;/p&gt;

&lt;p&gt;In the article I mentioned earlier, our beloved Jenkins was mentioned as well as some kind of microservice written in Java that was meant to trigger an analysis on SonarQube whenever a pull request was created or updated, based on a Bitbucket webhook.&lt;/p&gt;

&lt;p&gt;So... I tried writing a microservice myself using Node.JS and Express.js to trigger an analysis based on a pull request creation or update event, but I quickly found out that Bitbucket has support for so called &lt;a href="https://bitbucket.org/blog/faster-feedback-on-merges-with-pull-request-pipelines" rel="noopener noreferrer"&gt;"Pull Request Pipelines"&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I don't like over-engineering, but... It's my first instinct sometimes, and I fear that it's a curse...&lt;/em&gt; 💀👀&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using the blessing which are "Pull Request Pipelines"&lt;/strong&gt; 🙈💘&lt;br&gt;
When I figured out how the pull request pipelines worked, I was (still am) in love. What I ended up with was the pipeline configuration below 😍&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;pipelines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull-requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;**'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;step&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;SonarQube Analysis&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;newtmitch/sonar-scanner:4.0.0-alpine&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;sonar-scanner -Dsonar.projectBaseDir=$(pwd)&lt;/span&gt; 
              &lt;span class="s"&gt;-Dproject.settings=sonar.properties&lt;/span&gt;
              &lt;span class="s"&gt;-Dsonar.analysis.mode=issues&lt;/span&gt;
              &lt;span class="s"&gt;-Dsonar.bitbucket.repoSlug=$BITBUCKET_REPO_SLUG&lt;/span&gt; 
              &lt;span class="s"&gt;-Dsonar.bitbucket.accountName=$BITBUCKET_REPO_OWNER&lt;/span&gt; 
              &lt;span class="s"&gt;-Dsonar.bitbucket.branchName=$BITBUCKET_BRANCH&lt;/span&gt; 
              &lt;span class="s"&gt;-Dsonar.bitbucket.oauthClientKey=$OAUTH_CLIENT_KEY&lt;/span&gt; 
              &lt;span class="s"&gt;-Dsonar.bitbucket.oauthClientSecret=$OAUTH_CLIENT_SECRET&lt;/span&gt; 
              &lt;span class="s"&gt;-Dsonar.login=$SONAR_LOGIN&lt;/span&gt; 
              &lt;span class="s"&gt;-Dsonar.bitbucket.pullRequestId=$BITBUCKET_PR_ID&lt;/span&gt;
  &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;master&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;step&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;SonarQube Analysis&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;newtmitch/sonar-scanner:4.0.0-alpine&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;sonar-scanner -Dsonar.projectBaseDir=$(pwd)&lt;/span&gt;
              &lt;span class="s"&gt;-Dproject.settings=sonar.properties&lt;/span&gt;
              &lt;span class="s"&gt;-Dsonar.login=$SONAR_LOGIN&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Bitbucket has a bunch of &lt;a href="https://confluence.atlassian.com/bitbucket/variables-in-pipelines-794502608.html#Variablesinpipelines-Defaultvariables" rel="noopener noreferrer"&gt;pre-defined environment variables&lt;/a&gt; that you can use in these kind of situations. Environment variables that you need to define yourself are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SONAR_LOGIN&lt;/code&gt; which is a &lt;a href="https://docs.sonarqube.org/latest/user-guide/user-token/" rel="noopener noreferrer"&gt;SonarQube User Token&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;OAUTH_CLIENT_KEY&lt;/code&gt; and &lt;code&gt;OAUTH_CLIENT_SECRET&lt;/code&gt;require an &lt;a href="https://developer.atlassian.com/cloud/bitbucket/modules/oauth-consumer/" rel="noopener noreferrer"&gt;OAuth consumer&lt;/a&gt; to be configured with read access to the account and write access to pull requests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fd9a2wz0lamy1w8yvw6w2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fd9a2wz0lamy1w8yvw6w2.png" alt="Bitbucket OAuth Consumer Permissions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For some reason a "Callback URL" is required, otherwise you can't create the OAuth consumer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fi9zoqsn02o9mv4l1eqo2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fi9zoqsn02o9mv4l1eqo2.png" alt="Bitbucket OAuth Consumer Callback URL"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Telling SonarQube what project the repository is for!&lt;/strong&gt; 📃📌&lt;br&gt;
As you might have noticed within the command that I use to run the Sonar Scanner, it has an argument.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Dproject.settings=sonar.properties&lt;/code&gt; is the argument I'm talking about. It tells the scanner where to find the properties for this specific project, in this case it's the &lt;code&gt;sonar.properties&lt;/code&gt; file at the root of the repository.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

sonar.host.url=https://sonarqube.dev
sonar.projectKey=sonarqube-experimentation
sonar.projectName=SonarQube Experimentation


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

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;What are my next steps going to be in this journey?&lt;/strong&gt; 🐒&lt;br&gt;
What I noticed while I was researching, trying stuff out and miserably failing is that I had many individual resources but no way to consistently reproduce them.&lt;/p&gt;

&lt;p&gt;What would happen if something went wrong. I corrupt one of my DigitalOcean droplets that was running Docker with SonarQube for example? I couldn't easily spin up another and be certain that it would be exactly the same, I didn't really document the steps I took through the way of research &lt;/p&gt;

&lt;p&gt;Because of this I decided to become more familiar with the idea of &lt;em&gt;"Infrastructure as Code"&lt;/em&gt;, or shortened by the acronym &lt;em&gt;"IaC"&lt;/em&gt;!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Terraform kinda fell into my lap at this point&lt;/strong&gt; 👀💯&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.terraform.io" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt; is one of the tools in the HashiStack by &lt;a href="https://www.hashicorp.com" rel="noopener noreferrer"&gt;HashiCorp&lt;/a&gt;. It enables you to write your infrastructure in a declarative configuration, thus "Infrastructure as Code".&lt;/p&gt;

&lt;p&gt;I plan to write a different blog about it where I'll reflect onto the stuff I learned, mistakes I made and times I fell face-first into seaming unsolvable problems!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia1.tenor.com%2Fimages%2F5850e3f3ea819e382f80fa6750cf381f%2Ftenor.gif%3Fitemid%3D4902263" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia1.tenor.com%2Fimages%2F5850e3f3ea819e382f80fa6750cf381f%2Ftenor.gif%3Fitemid%3D4902263"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As always, I hope you learned something from my failures (💩), and if not, I'm praying that you at least had fun reading about them 🔥&lt;/p&gt;

</description>
      <category>sonarqube</category>
      <category>bitbucket</category>
      <category>devops</category>
    </item>
    <item>
      <title>Alertmanager configuration with PyYAML</title>
      <dc:creator>Julian</dc:creator>
      <pubDate>Mon, 09 Sep 2019 17:24:51 +0000</pubDate>
      <link>https://dev.to/serializator/alertmanager-configuration-with-pyyaml-and-include-1504</link>
      <guid>https://dev.to/serializator/alertmanager-configuration-with-pyyaml-and-include-1504</guid>
      <description>&lt;p&gt;&lt;strong&gt;What my nightmares were like trying to use multiple configuration files with Alertmanager&lt;/strong&gt; 💀👀&lt;/p&gt;

&lt;p&gt;I recently started working on monitoring some of the internal tools and services at &lt;a href="https://www.weprovide.com"&gt;We Provide&lt;/a&gt;. I decided to tinker with the "Prometheus, Grafana and Alertmanager" stack as a first attempt.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Obviously, there we some hiccups... (literally and figuratively)&lt;/em&gt; 💩&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xSrHYsiC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/lo8hgm03jfsac1yrgsy8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xSrHYsiC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/lo8hgm03jfsac1yrgsy8.jpg" alt="I over engineered..." width="880" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Originally I started thinking about a tool I could write, something like &lt;code&gt;am-stitcher&lt;/code&gt; (&lt;em&gt;ssooo original, not like &lt;code&gt;amtool&lt;/code&gt;, at all...&lt;/em&gt;), but... I realized that I was over-engineering something that was basically a matter of merging multiple YAML files into one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Google, here I come, gimme something useful or copy-pastable please! ❤️&lt;/strong&gt;&lt;br&gt;
I didn't really find anything sufficient for this use-case in terms of literally merging.&lt;/p&gt;

&lt;p&gt;Some of the tools I came across did what I wanted them to do, but didn't give me control over in what order to merge them. Some did, but they required me to specify every file individually instead of being able to recursively search a directory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PyYAML with pyyaml-include&lt;/strong&gt; 💌 💕&lt;br&gt;
I found an article about &lt;code&gt;!include&lt;/code&gt; in YAML, and as naive as I am, I thought it was something that might work in YAML natively... But, of course, it didn't!&lt;/p&gt;

&lt;p&gt;Support for &lt;code&gt;!include&lt;/code&gt; is added by the &lt;code&gt;pyyaml-include&lt;/code&gt; extension, which adds this "directive" to PyYAML.&lt;/p&gt;

&lt;p&gt;So... I tried installing PyYAML and the &lt;code&gt;pyyaml-include&lt;/code&gt; extension using PIP, hoping that the parsing with PyYAML would work with only the CLI, and it'd automatically use the &lt;code&gt;pyyaml-include&lt;/code&gt; extension. But I couldn't find a way to use PyYAML only in the CLI...&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I should really stop being so naive... Don't you think so too?&lt;/em&gt; 😬😅&lt;/p&gt;

&lt;p&gt;What I ended up writing was a small Python script that imports PyYAML and uses &lt;code&gt;pyyaml-include&lt;/code&gt; to parse &lt;code&gt;!include&lt;/code&gt; "directives".&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Don't be fooled, this was a lot of copy-paste in combination with trial and error!&lt;/em&gt; 🔥&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;yaml&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;yamlinclude&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;YamlIncludeConstructor&lt;/span&gt;

&lt;span class="n"&gt;YamlIncludeConstructor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_to_loader_class&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loader_class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FullLoader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getcwd&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;

&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Loader&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FullLoader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default_flow_style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I decided to use the standard input (&lt;code&gt;sys.stdin&lt;/code&gt;) and output (&lt;code&gt;sys.stdout&lt;/code&gt;) streams to read and write. I didn't want to bother with using arguments when I didn't even know the Python syntax 😅&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;python&lt;/span&gt; &lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;alertmanager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yml&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Shall we add a tiny bit of automation?!&lt;/strong&gt; 🙌&lt;/p&gt;

&lt;p&gt;At We Provide we mostly use Bitbucket, for open-source we use GitHub (obviously...), but for most of the stuff we use Bitbucket.&lt;/p&gt;

&lt;p&gt;It consists out of three steps, "Parse &amp;amp; Merge", "Validate" and "Deploy". I think those are kinda self-explanatory, don't you think? 😊&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;pipelines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;step&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;Parse &amp;amp; Merge&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;python:3.5.1&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;pip install pyyaml pyyaml-include&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;python parse-yaml.py &amp;lt; alertmanager.yml &amp;gt; merged.yml&lt;/span&gt;
        &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;merged.yml&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;step&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;Validate&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;serializator/amtool&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;amtool check-config merged.yml&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;step&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;Deploy&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;eeacms/rsync&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;rsync merged.yml $USER@$HOST:$ALERTMANAGER_YAML&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;curl -L -X POST $HOST$RELOAD_PATH&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, one of the images is &lt;code&gt;serializator/amtool&lt;/code&gt;. Originally I tried using the &lt;a href="https://hub.docker.com/r/metacloud/amtool-docker"&gt;&lt;code&gt;amtool-docker&lt;/code&gt; image from Cisco Metacloud (Anthony Rogliano)&lt;/a&gt;. But the fact that it was built on top of &lt;code&gt;golang:1.9-alpine3.6&lt;/code&gt; caused issues with the usage of &lt;code&gt;/bin/bash&lt;/code&gt; instead of &lt;code&gt;/bin/sh&lt;/code&gt; in the Bitbucket pipeline. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;^ If you know a solution for this, or if I overlooked something in the Bitbucket pipeline that I could've configured, please let me know!&lt;/em&gt; ❤️&lt;/p&gt;

&lt;p&gt;What I ended up doing was forking the repository and build the image on top of &lt;code&gt;golang:1.13-stretch&lt;/code&gt; instead of &lt;code&gt;golang:1.9-alpine3.6&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;^ I thought about the increase in size by not using the alpine tag, but it's negligible in my opinion, looking at the use-case.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I'm in love with the "/-/reload" endpoint&lt;/strong&gt; 🙈🙊&lt;br&gt;
I thought that I'd had to use SSH and use Docker Compose to restart the Alertmanager service, but it luckily didn't end up being that ugly.&lt;/p&gt;

&lt;p&gt;Somewhere in 2016 the &lt;code&gt;/-/reload&lt;/code&gt; endpoint was introduced by &lt;a href="https://github.com/ZhenyangZhao"&gt;ZhenyangZhao&lt;/a&gt;, and I'm glad he made that pull request! I don't know how I'd live with myself knowing that one of my deployments didn't have cURL in it...&lt;/p&gt;

&lt;p&gt;So, without much hesitation, I quickly wrote one line more to send a POST request to the &lt;code&gt;/-/reload&lt;/code&gt; endpoint, configured some stuff using environment variables, and pushed it to master!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I didn't know that this kinda happiness existed!&lt;/strong&gt; 😊🔥&lt;br&gt;
I finally laid down on bed, closed my eyes and began dreaming about the many files, folders and &lt;code&gt;!include&lt;/code&gt;'s I was going to write with a happy smile on my face! 😆&lt;/p&gt;

&lt;p&gt;I hope you got something useful out of this, and if not, I hope you at least enjoyed reading it!&lt;/p&gt;

&lt;p&gt;I thank you, &lt;a class="mentioned-user" href="https://dev.to/yamadashy"&gt;@yamadashy&lt;/a&gt; for &lt;a href="https://dev.to/yamadashy/emoji-cheatsheet-of-githubslackdiscordqiita-4d2f"&gt;this emoji cheatsheet ❤️&lt;/a&gt;&lt;/p&gt;

</description>
      <category>prometheus</category>
      <category>alertmanager</category>
      <category>devops</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>First job as a Software Engineer, without a degree...</title>
      <dc:creator>Julian</dc:creator>
      <pubDate>Sat, 27 Jul 2019 22:42:08 +0000</pubDate>
      <link>https://dev.to/serializator/first-job-as-a-software-engineer-without-a-degree-55b8</link>
      <guid>https://dev.to/serializator/first-job-as-a-software-engineer-without-a-degree-55b8</guid>
      <description>&lt;p&gt;Hi! As it says on my profile, my name is Julian, 19 and currently working as a Software Engineer at We Provide.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Software Engineer", "Full-Stack", "Web", "Computer Scientist?!"&lt;/strong&gt;&lt;br&gt;
"Software Engineer", honestly, it's only a term I use because I don't think "Web Developer" or "Full-Stack Software Engineer" (or any combination of those) suffice.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;^ I changed my mind, "Full Stack Engineer" sounds more right for what I do the most, web development, and maybe it even sounds prettier too!&lt;/em&gt; 🙊&lt;/p&gt;

&lt;p&gt;I like to tinker with a lot of stuff. I like to learn about algorithms and data structures, that doesn't make me a "Computer Scientist" or anything... I like working on monitoring stuff like Grafana with Prometheus, Logstash for centralized logging, that doesn't make me some kind of DevOps Engineer.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;ppssstt, if you know a better job description, tell me!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where I work 👀&lt;/strong&gt;&lt;br&gt;
Since I started working at &lt;a href="https://www.weprovide.com" rel="noopener noreferrer"&gt;We Provide&lt;/a&gt;, which as of this month is over a year ago, I've learned a lot. Definitely not only in a technical aspect, also socially and communicatively (is that a word).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;uhm, where is your degree?!&lt;/strong&gt; 🙄&lt;br&gt;
In my head, writing this feels a bit, egotistical? I mean, the way I imagine you reading this is &lt;em&gt;"wow, look at him, thinking that he is the only one who took this path and got somewhere!"&lt;/em&gt;. Immediately after, another thought came lurking around, what if I'm writing this to get to know myself a bit better and actually think about what happened the past two years?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I guess this is it, the blog post that's gonna make me ball up in a corner and cry&lt;/em&gt; 😜&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Somewhere end 2016&lt;/strong&gt; 🎆&lt;br&gt;
I started my education in &lt;em&gt;"Applicatie Ontwikkeling Niv. 4 (MBO)"&lt;/em&gt;, which translates to Software Engineering. &lt;em&gt;"Niv. 4 (MBO)"&lt;/em&gt; has a better explanation &lt;a href="https://en.wikipedia.org/wiki/Education_in_the_Netherlands#MBO" rel="noopener noreferrer"&gt;on Wikipedia&lt;/a&gt; than I could possibly give you!&lt;/p&gt;

&lt;p&gt;Throughout the time I spent at school, I didn't feel like I was really learning anything particularly new. I mostly did the work and helped other students. Though, I'm not saying it wasn't valuable to be there.&lt;/p&gt;

&lt;p&gt;I actually felt like I belonged, with people who shared the same interests as I. I can't say how much that helped me, coming from high school, not knowing what I was gonna do in a year, two years, three, and so forth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I guess, from mid 2017 till end 2017?&lt;/strong&gt; 🔥💩&lt;br&gt;
An internship... I had to go look for somewhere to do my internship... An internship on its own, love it, learn something in the real world instead of &lt;em&gt;"Let's build a TODO list in C#!"&lt;/em&gt;. What wasn't lovely about it was the fact that I was, and still am, not good in new environments (especially socially), and with new people who I'd have to be with for 8 hours a day. Scary!&lt;/p&gt;

&lt;p&gt;So... I found somewhere to do my internship, obviously had some nerves the night before, and was actually really nervous and anxious throughout that first day. &lt;em&gt;"Just have to get used to it"&lt;/em&gt;, I thought, nothing weird, everyone gets nervous on their first day, whether it's a little bit or like me.&lt;/p&gt;

&lt;p&gt;After a couple of days of thoroughly sweating (it also weren't the coldest days of the year), shivering and throwing up every morning, I decided that it wasn't for me. Because of the situation I was in I started thinking, why? Do I need to do this? Yes, if I want my college degree! &lt;/p&gt;

&lt;p&gt;Long story short, I sat down with my mentor and HR at the internship, and talked about what was going on. We also talked about where I saw myself in ... years, or any other reasonable time frame. After I told what I wanted to do and where I wanted to go, HR said something that I don't exactly remember but basically translates to:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I have ..., so I can't do ... anymore, sometimes you have to accept where you are.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We talked for a bit after that, came to the conclusion that the internship wasn't for me, and left. I talked with a couple people at my school who wanted to help me, but it eventually came down to the fact that I didn't see myself going through an internship where I'd shiver all day long, only for a piece of paper 4 years later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Somewhere mid 2018, I decided to apply for a job!&lt;/strong&gt; 📄&lt;br&gt;
 I finally got of my lazy ass, opened up Google (without getting of my ass, obviously...) and looked for &lt;em&gt;"developer job"&lt;/em&gt;. That was my first attempt, and I failed miserably! I quickly narrowed that down to &lt;em&gt;"web developer"&lt;/em&gt; and found a couple.&lt;/p&gt;

&lt;p&gt;What I ended up doing was getting into an (unknowingly) long-term relationship with a recruiter. I took one job interview using this strategy and I thought, nah... I really have to find something myself, I know what I want and I know it when I see it, not by getting a call about some potential employer who needs someone with "my skill set".&lt;/p&gt;

&lt;p&gt;I still get calls &lt;em&gt;"how is it going at your job, looking for something?!"&lt;/em&gt; 😢&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fglhp0o0jz6ittsesfzmr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fglhp0o0jz6ittsesfzmr.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Job interview at We Provide&lt;/strong&gt; 😄&lt;br&gt;
I eventually found We Provide, a web agency that was looking for a Back-end PHP developer, and didn't have a job description that looked like &lt;em&gt;"HTML, PHP, CSS, MySQL, .NET, MongoDB, Redis, Docker, AWS, Micro-Services"&lt;/em&gt; (and all the other terms you could put in there). It just looked like a fun and interesting place to work at, somewhere where I could keep learning and work on myself.&lt;/p&gt;

&lt;p&gt;I applied, soon enough got a response, and scheduled a job interview. What I remember most is the fact they thought that I had a degree, because I put "Applicatie Ontwikkeling Niv. 4 (MBO)" on my job application. I forgot that they probably wouldn't look at the years and see "2016-2017" and think "Did he finish that?".&lt;/p&gt;

&lt;p&gt;But the thing is, I don't remember that specifically because of it. I remember it because when we talked about it, it wasn't like &lt;em&gt;"it goes downhill from here, he has no degree, sorry, there is the door!"&lt;/em&gt;, they were, intrigued? I can't come up with a better word for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where I am now... ❤️&lt;/strong&gt;&lt;br&gt;
Right now, I don't think &lt;em&gt;"ugh, weekend is over, another day at school/work!"&lt;/em&gt;, I'm happy. I have a lot of fun with the people I work with and most importantly, I learn a lot.&lt;/p&gt;

&lt;p&gt;I want to clarify, I don't think that everyone should think "I don't need a degree!", if you can get one, do it. It depends on the situation. &lt;/p&gt;

&lt;p&gt;What I want to end with is, when someone doesn't have a degree, don't assume they didn't want it. Don't assume they are lazy or not motivated. I think that when you do something that you like, do it in your spare time and learn because of the fun of it, you can do so much.&lt;/p&gt;

&lt;p&gt;I thank you, &lt;a class="mentioned-user" href="https://dev.to/yamadashy"&gt;@yamadashy&lt;/a&gt; for &lt;a href="https://dev.to/yamadashy/emoji-cheatsheet-of-githubslackdiscordqiita-4d2f"&gt;this emoji cheatsheet 🙈&lt;/a&gt;&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>career</category>
    </item>
  </channel>
</rss>
