<?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: Pavel Kutáč</title>
    <description>The latest articles on DEV Community by Pavel Kutáč (@arxeiss).</description>
    <link>https://dev.to/arxeiss</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%2F455290%2F0724b500-6117-44b6-8757-87226ff16217.jpeg</url>
      <title>DEV Community: Pavel Kutáč</title>
      <link>https://dev.to/arxeiss</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arxeiss"/>
    <language>en</language>
    <item>
      <title>Dead code detection in Go monorepos with deadmono</title>
      <dc:creator>Pavel Kutáč</dc:creator>
      <pubDate>Thu, 29 Jan 2026 11:48:18 +0000</pubDate>
      <link>https://dev.to/arxeiss/dead-code-detection-in-go-monorepos-with-deadmono-3043</link>
      <guid>https://dev.to/arxeiss/dead-code-detection-in-go-monorepos-with-deadmono-3043</guid>
      <description>&lt;p&gt;Have you ever tried to clean up dead code in a Go monorepo with multiple services? Running &lt;code&gt;deadcode&lt;/code&gt; on each service separately gives you conflicting results, and you can't safely delete anything. But there's a better way.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;🇨🇿 V češtině si lze článek přečíst na &lt;a href="https://www.kutac.cz" rel="noopener noreferrer"&gt;kutac.cz&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;We maintain Go monorepo where multiple services share internal packages. Over time, code accumulates, and you want to clean up unused functions. But how do you know what's truly unused when you have about 10 services importing all different pacakges? &lt;/p&gt;

&lt;p&gt;There are linters &lt;a href="https://golangci-lint.run/docs/linters/configuration/#unused" rel="noopener noreferrer"&gt;unused in GolangCI&lt;/a&gt; or &lt;a href="https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/unusedfunc" rel="noopener noreferrer"&gt;unsedfunc in GoPLS&lt;/a&gt;. However those can detect only unexported unused functions within package. Go team also did a great tool called &lt;a href="https://pkg.go.dev/golang.org/x/tools/cmd/deadcode" rel="noopener noreferrer"&gt;deadcode&lt;/a&gt;. But it accept single entrypoint and checks for unreachable code from single main function.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with Simple Intersection
&lt;/h2&gt;

&lt;p&gt;You might think: "Just run &lt;code&gt;deadcode&lt;/code&gt; on each service and delete functions reported as dead in ALL services." This fails when services don't import the same packages.&lt;/p&gt;

&lt;p&gt;Consider this monorepo structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── services/
│   ├── service-a/     (imports pkg/cache, pkg/logging)
│   ├── service-b/     (imports pkg/cache, pkg/logging)
│   └── service-c/     (imports pkg/logging only)
└── pkg/
    ├── cache/
    │   ├── Get()      ✓ Used by service-a
    │   ├── Set()      ✓ Used by service-b
    │   └── Delete()   ✗ Actually dead
    └── logging/
        └── Info()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running &lt;code&gt;deadcode&lt;/code&gt; on each service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;service-a: Reports cache.Set(), cache.Delete() as dead
service-b: Reports cache.Get(), cache.Delete() as dead
service-c: Reports NOTHING about pkg/cache (doesn't import it!)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple intersection (dead in ALL services): &lt;strong&gt;EMPTY SET&lt;/strong&gt; ❌&lt;/p&gt;

&lt;p&gt;Why? &lt;code&gt;service-c&lt;/code&gt; doesn't import &lt;code&gt;pkg/cache&lt;/code&gt; at all, so it doesn't report anything about it. The intersection finds nothing, even though &lt;code&gt;cache.Delete()&lt;/code&gt; is genuinely unused by all services!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Package-Based Intersection
&lt;/h2&gt;

&lt;p&gt;I created &lt;a href="https://github.com/arxeiss/deadmono" rel="noopener noreferrer"&gt;&lt;code&gt;deadmono&lt;/code&gt;&lt;/a&gt; to solve this with &lt;strong&gt;package-based intersection&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Track which packages each service imports&lt;/li&gt;
&lt;li&gt;Analyze each entrypoint separately&lt;/li&gt;
&lt;li&gt;Intersect results per package (only among services that import it)&lt;/li&gt;
&lt;li&gt;Report truly dead functions&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install deadmono&lt;/span&gt;
go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/arxeiss/deadmono/cmd/deadmono@latest

&lt;span class="c"&gt;# Install the required deadcode tool&lt;/span&gt;
go &lt;span class="nb"&gt;install &lt;/span&gt;golang.org/x/tools/cmd/deadcode@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;deadmono services/authn/main.go services/config/main.go services/healthcheck/main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This analyzes all three services and reports functions unused by all of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works on the Example Above
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;For pkg/cache (imported by service-a, service-b):
  service-a reports: cache.Set(), cache.Delete() as dead
  service-b reports: cache.Get(), cache.Delete() as dead
  service-c: IGNORED (doesn't import pkg/cache)

  Intersection (service-a ∩ service-b):
    ✓ cache.Delete() - dead in BOTH services that import it

For pkg/logging (imported by all services):
  All services use logging.Info()
  Intersection: (empty - no dead functions)

Final Result:
  pkg/cache/cache.go:15:1: unreachable func: Delete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can confidently delete &lt;code&gt;cache.Delete()&lt;/code&gt; knowing it's truly unused.&lt;/p&gt;

&lt;h2&gt;
  
  
  Useful Flags
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-test&lt;/code&gt; - Analyze test executables too&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-generated&lt;/code&gt; - Include dead functions in generated files&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-tags string&lt;/code&gt; - Comma-separated list of build tags&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-filter string&lt;/code&gt; - Filter packages by regex (default: current module)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-json&lt;/code&gt; - Output in JSON format&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-debug&lt;/code&gt; - Verbose debug output&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Multiple Go Modules
&lt;/h3&gt;

&lt;p&gt;By default, all entrypoints must be in the same module. To analyze across modules, you must provide &lt;code&gt;-filter&lt;/code&gt; flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;deadmono &lt;span class="nt"&gt;-filter&lt;/span&gt; &lt;span class="s2"&gt;"github.com/myorg/.*"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  module1/services/api/main.go &lt;span class="se"&gt;\&lt;/span&gt;
  module2/services/worker/main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;If you maintain Go monorepos, give &lt;a href="https://github.com/arxeiss/deadmono" rel="noopener noreferrer"&gt;&lt;code&gt;deadmono&lt;/code&gt;&lt;/a&gt; a try. It's open source and contributions are welcome!&lt;/p&gt;

</description>
      <category>go</category>
      <category>cleancode</category>
      <category>showdev</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Get GitHub PR from commit hash</title>
      <dc:creator>Pavel Kutáč</dc:creator>
      <pubDate>Tue, 03 Jun 2025 12:38:54 +0000</pubDate>
      <link>https://dev.to/arxeiss/get-github-pr-from-commit-hash-i9c</link>
      <guid>https://dev.to/arxeiss/get-github-pr-from-commit-hash-i9c</guid>
      <description>&lt;p&gt;Have you ever needed to get GitHub PR details from the commit hash in the master branch? With the Merge commits strategy, number of PR will be in the commit message. But what about Rebase and merge or Squash and merge strategy?&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;🇨🇿 V češtině si lze článek přečíst na &lt;a href="https://www.kutac.cz/pocitace-a-internety/jak-ziskat-github-pr-z-commit-hashe" rel="noopener noreferrer"&gt;kutac.cz&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Lately, I have modified GitHub Action, which sends Slack message with the author of the last PR that was merged. But how to get the PR number and the author of that PR when I only have a log history?&lt;/p&gt;

&lt;h2&gt;
  
  
  Merge commit strategy
&lt;/h2&gt;

&lt;p&gt;As I wrote above, when using the Merge commit strategy, GitHub creates a merge commit with a message that looks 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;Merge pull request #270 from author/branch-name

fix: here is your original commit message
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here it is easy to extract the number. But before merging, the user can modify the message and the number of PR will be gone or different. &lt;/p&gt;

&lt;h2&gt;
  
  
  I have only commit hash
&lt;/h2&gt;

&lt;p&gt;Browsing in git history with &lt;code&gt;git log&lt;/code&gt; or similar, will not tell you much. But with GitHub API, you can ask which PR contains some commit hash.&lt;/p&gt;

&lt;p&gt;And the best about that is, it works well in all scenarios! Including &lt;strong&gt;Rebase and merge&lt;/strong&gt; and &lt;strong&gt;Squash and merge&lt;/strong&gt; strategies, which create new commit. So the commit in the original PR and in the master branch have different hash.&lt;/p&gt;

&lt;p&gt;The script below uses &lt;a href="https://cli.github.com" rel="noopener noreferrer"&gt;GitHub CLI&lt;/a&gt; which is pre-installed inside GitHub Actions, and by setting &lt;code&gt;GH_TOKEN&lt;/code&gt; also authenticated.&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;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;your-action-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="c1"&gt;# We are going to history a bit, so load little more to be sure&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;Get last PR author&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;release-author&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;GH_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.token }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;# Get last commit SHA&lt;/span&gt;
          &lt;span class="s"&gt;# Don't use --no-merges if you use Merge strategy&lt;/span&gt;
          &lt;span class="s"&gt;# Don't use --skip 1 unless really needed (we need that)&lt;/span&gt;
          &lt;span class="s"&gt;last_commit_sha=$(git --no-pager log --format=%H --no-merges -n 1 --skip 1) || last_commit_sha=""&lt;/span&gt;
          &lt;span class="s"&gt;author="unknown"&lt;/span&gt;

          &lt;span class="s"&gt;if [ -n "$last_commit_sha" ]; then&lt;/span&gt;
            &lt;span class="s"&gt;author=$(gh pr list --search $last_commit_sha --state merged --json author --jq '.[0].author.login') || author=""&lt;/span&gt;
          &lt;span class="s"&gt;fi&lt;/span&gt;

          &lt;span class="s"&gt;echo "author: $author"&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  I need to explain script above
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;git --no-pager log --format=%H --no-merges -n 1 --skip 1&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;git log&lt;/code&gt; operates on top of the git history&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--no-pager&lt;/code&gt; turns off pagination - not necessary&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--format=%H&lt;/code&gt; prints only full commit hash&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--no-merges&lt;/code&gt; will skip merge commits - only if using Rebase and merge or Squash and merge&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-n 1 --skip 1&lt;/code&gt; instruct to return only 1 commit (&lt;code&gt;-n&lt;/code&gt;) and &lt;code&gt;--skip&lt;/code&gt; last one. This is important for us, as the last commit is created by &lt;a href="http://github.com/GoogleCloudPlatform/release-please-action" rel="noopener noreferrer"&gt;Release please action&lt;/a&gt;. Remove skipping if not needed for you.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;gh pr list --search $last_commit_sha --state merged --json author --jq '.[0].author.login'&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;gh pr list&lt;/code&gt; is using &lt;a href="https://cli.github.com" rel="noopener noreferrer"&gt;GitHub CLI&lt;/a&gt; to list PRs, already pre-installed in GitHub action&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--search $last_commit_sha&lt;/code&gt; is a general search query, so we are searching by our commit hash &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--state merged&lt;/code&gt; include only merged PRs&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--json author --jq '.[0].author.login'&lt;/code&gt; tells the CLI tool to return JSON with the author field, which is then filtered by &lt;a href="https://jqlang.org" rel="noopener noreferrer"&gt;&lt;code&gt;jq&lt;/code&gt; expression&lt;/a&gt; to give us the login of the first author&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Is this bulletproof?
&lt;/h3&gt;

&lt;p&gt;No, it is not, because it is using a generic search query. So if someone adds commit directly to comments, this will fail.&lt;/p&gt;

&lt;p&gt;In that case, you can improve a query a bit to exclude comments from search &lt;code&gt;--search "{$last_commit_sha} \!in:comments"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Or you can select author together with commits of PR, and then using JQ filter it out. It is however more complex, but also more reliable not to give false positives.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh &lt;span class="nb"&gt;pr &lt;/span&gt;list &lt;span class="nt"&gt;--search&lt;/span&gt; &lt;span class="nv"&gt;$last_commit_sha&lt;/span&gt; &lt;span class="nt"&gt;--state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;merged &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--json&lt;/span&gt; author,commits &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--jq&lt;/span&gt; &lt;span class="s1"&gt;'.[] | select(.commits[]?.oid == "'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$last_commit_sha&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;'") | .author.login'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you spot another possible issue, share it with us in comments.&lt;/p&gt;

</description>
      <category>git</category>
      <category>githubactions</category>
      <category>github</category>
      <category>devops</category>
    </item>
    <item>
      <title>Automated code review requests with code owners</title>
      <dc:creator>Pavel Kutáč</dc:creator>
      <pubDate>Tue, 11 Oct 2022 09:01:07 +0000</pubDate>
      <link>https://dev.to/arxeiss/automated-code-review-requests-with-code-owners-2238</link>
      <guid>https://dev.to/arxeiss/automated-code-review-requests-with-code-owners-2238</guid>
      <description>&lt;p&gt;Tests, linters, build. All those and much more are very often automated in CI/CD. But you can go further and automate assigning developers to do a code review of PR. And you can specify different code owners of different parts of code as well.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;🇨🇿 V češtině si lze článek přečíst na &lt;a href="https://www.kutac.cz/pocitace-a-internety/automaticke-zadosti-o-code-review-s-codeowners" rel="noopener noreferrer"&gt;kutac.cz&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Code owners are not a new feature, it was introduced on the &lt;a href="https://github.blog/2017-07-06-introducing-code-owners/" rel="noopener noreferrer"&gt;GitHub blog&lt;/a&gt; already in the year 2017. But for small teams or individuals, this might not be so useful. So I learned about it just now.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Gitlab supports code owners too, but only on a paid plan. So I wasn't able to test it. However, it should be the same, according to &lt;a href="https://docs.gitlab.com/ee/user/project/code_owners.html" rel="noopener noreferrer"&gt;their documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What are Code owners and why you want to define them?
&lt;/h2&gt;

&lt;p&gt;With code owners, you can define individual developer or the whole team, who is responsible for the code. When a new PR is created, it will automatically assign code owners to do a review, if some files owned by them are changed. &lt;/p&gt;

&lt;p&gt;And later, in branch settings, you can specify &lt;strong&gt;Required review from code owners&lt;/strong&gt; rule. And no one will be able to merge the code, until some owner approves PR.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F35n5a95nifzmhn3vvq0b.jpg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F35n5a95nifzmhn3vvq0b.jpg" alt="Branch rule requires approval from Code owner"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The team as a code owner
&lt;/h3&gt;

&lt;p&gt;You can specify also the whole team as code owner. Then the whole team is assigned to do a code review. But not an individual developer. To assign individual dev, you have to turn on this feature in team settings.&lt;/p&gt;

&lt;p&gt;You can also select a member of the team, which is never assigned to do a review. In our team, it is the main architect. He is still code owner, because the team is code owner. But is not assigned to review.&lt;/p&gt;

&lt;p&gt;Unless you specify him directly as code owner of some file. If that file is changed, he is assigned as reviewer. But not as a team member, but as code owner.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F20uaal89u9d9levcs4s1.jpg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F20uaal89u9d9levcs4s1.jpg" alt="Setting of code review per team"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Define code owners
&lt;/h2&gt;

&lt;p&gt;The config of code owners is in &lt;code&gt;.github/CODEOWNERS&lt;/code&gt; file. Each line contains a file pattern followed by one or more developers or teams as owners. Later match takes precedence over the earlier rule.&lt;/p&gt;

&lt;p&gt;File pattern is the same as in &lt;code&gt;.gitignore&lt;/code&gt; file and is documented in &lt;a href="https://git-scm.com/docs/gitignore#_pattern_format" rel="noopener noreferrer"&gt;Pattern format section&lt;/a&gt; of git documentation.&lt;/p&gt;

&lt;p&gt;The final CODEOWNERS file might look like this including comments:&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;# Global rule set for everything. And special cases exclude later&lt;/span&gt;
&lt;span class="k"&gt;*&lt;/span&gt; @mycompany/be-code-reviewers

&lt;span class="c"&gt;# Pipeline files are owned by Octocat&lt;/span&gt;
.github/workflows/ @octocat

&lt;span class="c"&gt;# Index and htaccess.&lt;/span&gt;
&lt;span class="c"&gt;# This rules override previous one. Need to specify all of them if required.&lt;/span&gt;
/index.php @mycompany/be-code-reviewers @octocat
/.htaccess @mycompany/be-code-reviewers @octocat

&lt;span class="c"&gt;# Dependencies&lt;/span&gt;
composer.json @arxeiss
composer.lock @arxeiss
/vendor/ @arxeiss

&lt;span class="c"&gt;# You can also remove code owner if needed.&lt;/span&gt;
/CHANGELOG.md
/version.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Cover image is taken from &lt;a href="https://github.blog/2017-07-06-introducing-code-owners/" rel="noopener noreferrer"&gt;GitHub blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>github</category>
      <category>productivity</category>
      <category>git</category>
    </item>
    <item>
      <title>Shell conditions in Gitlab CI</title>
      <dc:creator>Pavel Kutáč</dc:creator>
      <pubDate>Tue, 06 Sep 2022 16:24:02 +0000</pubDate>
      <link>https://dev.to/arxeiss/shell-conditions-in-gitlab-ci-545</link>
      <guid>https://dev.to/arxeiss/shell-conditions-in-gitlab-ci-545</guid>
      <description>&lt;p&gt;Scripts in Gitlab CI/CD might contain conditions, and there are multiple options for how to solve that. But which Shell is used and which syntax should be used then? This is not documented.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;🇨🇿 V češtině si lze článek přečíst na &lt;a href="https://www.kutac.cz/pocitace-a-internety/shell-podminky-v-gitlab-ci"&gt;kutac.cz&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;blockquote&gt;
&lt;p&gt;If you are using a different executor than Docker, this might not be relevant for you. However, Docker executor is the default one for shared runners in gitlab.com&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In &lt;code&gt;gitlab-ci.yml&lt;/code&gt; file you specify the Docker image and also commands to execute. You can include conditions or other constructs in those commands. But used Docker image might contain multiple Shell implementations and it is necessary to know which Shell is selected and which syntax to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Manually specifying Shell in &lt;code&gt;*.sh&lt;/code&gt; file
&lt;/h2&gt;

&lt;p&gt;If the script is not written directly in &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; file, but in an external one, you can specify the path to Shell and the path to the file. Then you don't care much which Shell is selected in the executor. The pipeline might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;registry.gitlab.com/pavel.kutac/docker-ftp-deployer:php81&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;/bin/bash /usr/local/bin/execute-deployment.sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Condition directly inside &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; file
&lt;/h2&gt;

&lt;p&gt;You can write conditions directly into YAML with &lt;a href="https://yaml.org/spec/1.2-old/spec.html#style/block/literal"&gt;Literal block style&lt;/a&gt;. Then the pipeline might look like this. Used script and Docker image are taken from &lt;a href="https://dev.to/arxeiss/parallel-incremental-ftp-deploy-in-ci-pipeline-2511"&gt;Parallel incremental FTP Deployer&lt;/a&gt;. But now you need to know, which syntax you should use.&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;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;registry.gitlab.com/pavel.kutac/docker-ftp-deployer:php81&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;if [[ ${CI_COMMIT_MESSAGE,,} != "[skip-maintenance]"* ]]; then&lt;/span&gt;
      &lt;span class="s"&gt;wget --no-verbose -O - https://kutac.cz/some/path/to/turn/on/maintenance-mode&lt;/span&gt;
    &lt;span class="s"&gt;fi&lt;/span&gt;
    &lt;span class="s"&gt;./.ci/deploy/run-lftp-incremental-sync.sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But how to get that info? I wasn't able to find it in the documentation. And mentioned Docker image contains Busybox &lt;code&gt;sh&lt;/code&gt; and also &lt;code&gt;bash&lt;/code&gt;. I had to dig into the source code of Gitlab runner.&lt;/p&gt;

&lt;p&gt;And there it is clear, that Gitlab Runner first executes a short script with &lt;code&gt;/bin/sh&lt;/code&gt;. &lt;code&gt;BashDetectShellScript&lt;/code&gt; below checks different paths for different shell implementation and select one of them. The selected one is then used to execute all scripts from &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;BashDetectShellScript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;`if [ -x /usr/local/bin/bash ]; then
    exec /usr/local/bin/bash $@
elif [ -x /usr/bin/bash ]; then
    exec /usr/bin/bash $@
elif [ -x /bin/bash ]; then
    exec /bin/bash $@
elif [ -x /usr/local/bin/sh ]; then
    exec /usr/local/bin/sh $@
elif [ -x /usr/bin/sh ]; then
    exec /usr/bin/sh $@
elif [ -x /bin/sh ]; then
    exec /bin/sh $@
elif [ -x /busybox/sh ]; then
    exec /busybox/sh $@
else
    echo shell not found
    exit 1
fi

`&lt;/span&gt;
&lt;span class="c"&gt;// ...&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;BashShell&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetConfiguration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="n"&gt;common&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ShellScriptInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;common&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ShellConfiguration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// ...&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;common&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LoginShell&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CmdLine&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;" -l"&lt;/span&gt;
        &lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Arguments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"-l"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DockerCommand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"sh"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"-c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReplaceAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BashDetectShellScript&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"$@"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"-l"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DockerCommand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"sh"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"-c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReplaceAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BashDetectShellScript&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"$@"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>git</category>
      <category>devops</category>
      <category>linux</category>
    </item>
    <item>
      <title>Raspberry Pi as wireless audio receiver</title>
      <dc:creator>Pavel Kutáč</dc:creator>
      <pubDate>Tue, 26 Apr 2022 09:01:14 +0000</pubDate>
      <link>https://dev.to/arxeiss/raspberry-pi-as-wireless-audio-receiver-51j5</link>
      <guid>https://dev.to/arxeiss/raspberry-pi-as-wireless-audio-receiver-51j5</guid>
      <description>&lt;p&gt;How to turn old Hi-Fi tower into a modern audio speakers, with wireless connectivity? Use Raspberry Pi&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;🇨🇿 V češtině si lze článek přečíst na &lt;a href="https://www.kutac.cz/pocitace-a-internety/raspberrypi-jako-bluetooth-audio-prehravac"&gt;kutac.cz&lt;/a&gt; &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My parents wanted to throw out old, but still functional, Hi-Fi tower. So I took it into office. However, only place where it fits is on the other side of room than tables are. I wanted to avoid laying cable on the ground and covering with duct tape. So I used Raspberry Pi, which was &lt;a href="https://dev.to/arxeiss/story-of-automated-fish-feeder-with-raspberrypi-25i3"&gt;streaming fish in aquarium&lt;/a&gt; during Christmas time. And turned it into Bluetooth receiver.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bluetooth, AirPlay and Spotify Connect
&lt;/h2&gt;

&lt;p&gt;I used nice prepared script, which guides you through the process and install all required. I tested it only with Bluetooth, but it supports different types of connectivity. Script can be found on &lt;a href="https://github.com/nicokaiser/rpi-audio-receiver"&gt;github.com/nicokaiser/rpi-audio-receiver&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After executing the script, I restarted Raspberry. Then I saw my Raspberry in the list of available Bluetooth devices on my laptot. The name of the device is the same as &lt;code&gt;hostname&lt;/code&gt;, which might be changed during the script wizard as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  USB Bluetooth dongle is required
&lt;/h2&gt;

&lt;p&gt;In the first step, I tried to use internal &lt;strong&gt;onboard Bluetooth&lt;/strong&gt;. The Readme of the repo &lt;strong&gt;discourage&lt;/strong&gt; from using internal one, and I do as well. It most likely will not work, or have issues. I was able to connect, but wasn't able to play music. And second time, I wasn't able to connect anymore.&lt;/p&gt;

&lt;p&gt;Later, I connected old USB dongle with Bluetooth v2.0. It was working immediately, but I had to &lt;strong&gt;manually pair devices&lt;/strong&gt;. And it was causing some issues with my laptop, when I connected also wireless mouse. So I bought new USB dongle with Bluetooth v4.2. Since then, I don't need to manually pair devices and it &lt;strong&gt;works like a charm!&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Manually pairing devices
&lt;/h2&gt;

&lt;p&gt;Sometimes you might be asked to &lt;strong&gt;enter PIN&lt;/strong&gt; on the device, on the Raspberry. That is not easy, especially when you don't have monitor and keyboard connected. So I suggest to use SSH and connect remotely. Start Bluetooth interactive console with &lt;code&gt;sudo bluetoothctl&lt;/code&gt; and do the steps below.&lt;/p&gt;

&lt;p&gt;After starting console, you might see all MAC addresses that are trying to connect. Try to connect from your phone or laptop and you will see the address there. Copy it, you will need that.&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;# Type this after starting 'sudo bluetoothctl'&lt;/span&gt;
power on
agent on
pair 00:00:00:00:00:00 &lt;span class="c"&gt;# Put here MAC address of your device&lt;/span&gt;
&lt;span class="c"&gt;# Here you will be asked to enter PIN&lt;/span&gt;
trust 00:00:00:00:00:00 &lt;span class="c"&gt;# Put here MAC address of your device as well&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>raspberrypi</category>
    </item>
    <item>
      <title>Gitlab runner on Raspberry Pi</title>
      <dc:creator>Pavel Kutáč</dc:creator>
      <pubDate>Wed, 23 Mar 2022 12:01:09 +0000</pubDate>
      <link>https://dev.to/arxeiss/gitlab-runner-on-raspberry-pi-2p7h</link>
      <guid>https://dev.to/arxeiss/gitlab-runner-on-raspberry-pi-2p7h</guid>
      <description>&lt;p&gt;There are many reasons why to run Gitlab runner on your own machine. And here is the tutorial how to do it with Raspberry Pi. Including installation of Docker, which is most likely needed.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;🇨🇿 V češtině si lze článek přečíst na &lt;a href="https://www.kutac.cz/pocitace-a-internety/gitlab-runner-na-raspberrypi" rel="noopener noreferrer"&gt;kutac.cz&lt;/a&gt; &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The first step is to install Raspberry Pi OS to SD card. That is actually super easy. Just download and run &lt;strong&gt;Raspberry Pi Imager&lt;/strong&gt; from &lt;a href="https://www.raspberrypi.com/software/" rel="noopener noreferrer"&gt;raspberrypi.com/software&lt;/a&gt;. Select recommended version, target SD card, and start writing process. Then insert the SD card into Raspberry Pi and plug power. After Raspberry Pi OS will start, it suggests you to change password, connect to WiFi and install updates. I highly recommend to do all of that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8bdaae1yzmk5qatzooz3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8bdaae1yzmk5qatzooz3.png" alt="Raspberry Pi OS installation" width="682" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Install Docker and git
&lt;/h2&gt;

&lt;p&gt;Docker will be most likely needed, while git is required. Luckily, git should be already preinstalled. Just try to run &lt;code&gt;git --version&lt;/code&gt; to confirm that. The preferred way of installing Docker is via &lt;code&gt;apt&lt;/code&gt;, however, that is not supported for Raspberry Pi. It is required to use &lt;a href="https://docs.docker.com/engine/install/debian/#install-using-the-convenience-script" rel="noopener noreferrer"&gt;the Convenience script&lt;/a&gt;. You can download and execute it with the command below. Because Docker requires &lt;code&gt;root&lt;/code&gt; access, it is suggested to add the current user into the &lt;a href="https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user" rel="noopener noreferrer"&gt;docker group&lt;/a&gt;. Also included in the script below.&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;# Downloads script and start installation&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://get.docker.com &lt;span class="nt"&gt;-o&lt;/span&gt; get-docker.sh
&lt;span class="nb"&gt;sudo &lt;/span&gt;sh get-docker.sh
&lt;span class="c"&gt;# Adds current user to docker group&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;groupadd docker &lt;span class="c"&gt;# Most likely returns an error, that group already exists&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;groups&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="c"&gt;# List all groups, current user belongs to&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg0b2iovnab2piyb2hd0c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg0b2iovnab2piyb2hd0c.png" alt="Installing Docker" width="640" height="230"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After restarting, it is possible to test if Docker is running. Just execute &lt;code&gt;docker run hello-world&lt;/code&gt;. It should print out some basic information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install Gitlab runner
&lt;/h2&gt;

&lt;p&gt;Gitlab runner can be installed via script similarly to Docker. However, it ends up with an error, that the current distribution is not supported. Raspberry Pi OS is based on Debian but has its own ID. So it is required to print out the current OS version and pass some values into the script.&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;# Prints out current OS version&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/os-release
&lt;span class="c"&gt;# Change os and dist variable with values from output above&lt;/span&gt;
curl &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="s2"&gt;"https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh"&lt;/span&gt; | &lt;span class="nb"&gt;sudo &lt;/span&gt;&lt;span class="nv"&gt;os&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;debian &lt;span class="nv"&gt;dist&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;bullseye bash
&lt;span class="c"&gt;# Now you can install Gitlab runner&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;gitlab-runner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fin39oykc3g4lrfnojchz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fin39oykc3g4lrfnojchz.png" alt="Add Gitlab APT repository and install Gitlab runner" width="641" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Register Gitlab runner
&lt;/h2&gt;

&lt;p&gt;The last step is to register Gitlab runner. One installation can handle multiple projects or project groups. The first step is to copy the registration token in CI/CD settings of project or group. It might be worth disabling shared runners too.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcfck4jjvdxw9qde5spp4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcfck4jjvdxw9qde5spp4.png" alt="Gitlab CI/CD settings" width="800" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To start the registration process execute &lt;code&gt;sudo gitlab-runner register&lt;/code&gt; command. It asks for multiple parameters and you should enter docker for executor parameter.&lt;/p&gt;

&lt;p&gt;In newer version it might be necessary to run also &lt;code&gt;sudo gitlab-runner install&lt;/code&gt; to install it as system service. Or use &lt;code&gt;sudo gitlab-runner run&lt;/code&gt; to run until terminal is closed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftdd6m84pqd10fvil07p9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftdd6m84pqd10fvil07p9.png" alt="Register Gitlab runner" width="642" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Issues with custom Docker image
&lt;/h2&gt;

&lt;p&gt;If you are using in pipeline custom Docker image, you might be facing issues with incompatibility. Your computer is most likely amd64 architecture, while Raspberry is arm architecture. So your docker image will not work on Raspberry. You have to build docker image on Raspberry directly or use &lt;code&gt;buildx&lt;/code&gt; extension. I did this for my &lt;a href="https://gitlab.com/pavel.kutac/docker-ftp-deployer/-/commit/5f0b84152600ec35d992b070aa298c555025fd18" rel="noopener noreferrer"&gt;Docker FTP Deployer&lt;/a&gt; repository. You can find more about Docker FTP Deployer in my article &lt;a href="https://dev.to/arxeiss/parallel-incremental-ftp-deploy-in-ci-pipeline-2511"&gt;Parallel incremental FTP deploy in CI pipeline&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>git</category>
      <category>raspberrypi</category>
      <category>tutorial</category>
      <category>docker</category>
    </item>
    <item>
      <title>In Go nil is not equal to nil, sometimes</title>
      <dc:creator>Pavel Kutáč</dc:creator>
      <pubDate>Tue, 22 Feb 2022 10:01:00 +0000</pubDate>
      <link>https://dev.to/arxeiss/in-go-nil-is-not-equal-to-nil-sometimes-jn8</link>
      <guid>https://dev.to/arxeiss/in-go-nil-is-not-equal-to-nil-sometimes-jn8</guid>
      <description>&lt;p&gt;In some cases, Go returns false when comparing nil variable and nil. This might be confusing for beginners. But this leads to some benefits too.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;🇨🇿 V češtině si lze článek přečíst na &lt;a href="https://www.kutac.cz/pocitace-a-internety/v-go-se-nil-nerovna-nil-nekdy"&gt;kutac.cz&lt;/a&gt; &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The following code is a nice example when variable &lt;code&gt;val&lt;/code&gt; is &lt;code&gt;nil&lt;/code&gt; but the comparison in the function returns false. A similar situation is with &lt;code&gt;readFile&lt;/code&gt; function, which returns &lt;code&gt;error&lt;/code&gt;. You can run code in &lt;a href="https://go.dev/play/p/X73qPwOQ50J"&gt;Go Playground&lt;/a&gt; to prove to yourself it is true.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;nilPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"I got nil"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"I got %T type with value '%v'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PathError&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Prints: I got string type with value 'Some value'&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nilPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Some value"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="c"&gt;// Prints: I got nil&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nilPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="c"&gt;// Prints: true&lt;/span&gt;
    &lt;span class="c"&gt;// but then: I got *string type with value '&amp;lt;nil&amp;gt;'&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Prints true&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nilPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;With error interface"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// Prints: true (inside readFile)&lt;/span&gt;
    &lt;span class="c"&gt;// but then: false (in main method)&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The reason of this is interface
&lt;/h2&gt;

&lt;p&gt;This behavior is caused by using &lt;code&gt;interface{}&lt;/code&gt; as an input type. Which is something like &lt;strong&gt;Any&lt;/strong&gt;. The second function returns &lt;code&gt;error&lt;/code&gt;, which is also interface. The &lt;br&gt;
comparison then returns &lt;code&gt;false&lt;/code&gt;, because the value is null, but it has also data type. That is the reason why it is not equal to "raw" &lt;code&gt;nil&lt;/code&gt;. If you are comparing variable with a known type, you are not affected by this behavior.&lt;/p&gt;
&lt;h3&gt;
  
  
  Solution with reflection
&lt;/h3&gt;

&lt;p&gt;The way to fix the function which is returning &lt;code&gt;error&lt;/code&gt; is simple. Do not set concrete error type, but stay with &lt;code&gt;error&lt;/code&gt; type. So changing first line to this &lt;code&gt;var err error&lt;/code&gt;. That is also best practise.&lt;/p&gt;

&lt;p&gt;In the second case it might be little bit trickier. Changing data type is not so easy, because we want to accept anything. So the reflection package must be used here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValueOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Kind&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ptr&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValueOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsNil&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"I got nil"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Calling methods on nil object
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;I'm not sure this is related to behavior described above. But in my eyes it is similar.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Go doesn't have methods like other OOP languages. In Go, we call it &lt;strong&gt;receivers&lt;/strong&gt;, which acts similarly. But there is a main difference. If the variable of type is nil, you can still call its receivers, or methods if you want. The code below does not panic but works correctly. Check it on &lt;a href="https://go.dev/play/p/bHKhx8Q_z-_5"&gt;Go Playground&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;JSONParser&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;JSONParser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;JSONParser&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="c"&gt;// Prints: 0&lt;/span&gt;

    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;JSONParser&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;`{"just": "show", "some_result": 255}`&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="c"&gt;// Prints: 36&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>go</category>
      <category>todayilearned</category>
      <category>codenewbie</category>
    </item>
    <item>
      <title>Story of automated fish feeder with RaspberryPi</title>
      <dc:creator>Pavel Kutáč</dc:creator>
      <pubDate>Tue, 01 Feb 2022 10:01:25 +0000</pubDate>
      <link>https://dev.to/arxeiss/story-of-automated-fish-feeder-with-raspberrypi-25i3</link>
      <guid>https://dev.to/arxeiss/story-of-automated-fish-feeder-with-raspberrypi-25i3</guid>
      <description>&lt;p&gt;All started 2 years ago, when a colleague got a Beta fish and brought it to the office. By that day all of us started to learn how to care about aquarium. But our office building is closed every Christmas for almost 2 weeks. That might be too much for the fish without food.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;🇨🇿 V češtině si lze článek přečíst na &lt;a href="https://www.kutac.cz/co-na-srdci-to-na-blogu/automaticke-krmitko-do-akvaria-s-raspberrypi"&gt;kutac.cz&lt;/a&gt; &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Before Christmas 2020 we used our smaller tank and moved the fish to a colleague's flat. But during 2021 that tank became an &lt;a href="https://www.profiq.com/linux-aquarium-pc/"&gt;Aquarium PC&lt;/a&gt;. So there was no spare tank to move the fish again. Automated fish feeder is must have. But the existing ones are either expensive or not compatible with our aquarium.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For more about side projects in our company, visit &lt;a href="https://www.profiq.com/blog/"&gt;profiq.com/blog&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3D printed solution
&lt;/h2&gt;

&lt;p&gt;On &lt;a href="https://www.thingiverse.com/thing:2089866"&gt;ThingiVerse.com&lt;/a&gt; we have found ideal solution. First, we had to do a small modification to properly attach stepper motor. The first prototype actually worked! But in the tank, we have also snail, not just fish. And the snail’s food was bigger than the bowl in the feeder. So we needed to do the next iteration and print the inner part again with bigger bowls. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7_-KI_yh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r1w3a2l6szlv3o0ko98f.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7_-KI_yh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r1w3a2l6szlv3o0ko98f.jpg" alt="Detail of feeder with bowls and stepper motor" width="880" height="660"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Android Things vs RaspberryPi
&lt;/h2&gt;

&lt;p&gt;I got an Android Things kit during Google Developer Days back in 2017 in Krakow, Poland. And I thought I could use it for feeder. However, Google stopped this project meanwhile. I thought it means, that only new features are not coming. But they removed also all images required to make the kit alive. I spent the whole weekend trying to find another solution to make it work, but unsuccessfully. So with colleagues, we decided to use the RaspberryPi which was unused in our office.&lt;/p&gt;

&lt;p&gt;After we prepared RaspberryPi and we were able to control LED, we realized what will be the next challenge. The stepper motor driver needs input voltage in the range 8.2 - 45V and a logic voltage between 2.5V and 5.25V. But RaspberryPi needs 5V and has 3.3V GPIO pins. And we had an old laptop power adapter that produce 19.5V. The decision was made. We will use only 1 power supply and the feeder will contain also DC/DC step-down module to power Raspberry.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--udwfVr_F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5lryjw2fad8j373i2ry6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--udwfVr_F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5lryjw2fad8j373i2ry6.png" alt="Schema of electrical circuit" width="620" height="620"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  CRON with Python script and monitoring
&lt;/h2&gt;

&lt;p&gt;RaspberryOS comes with some nice integration into Python, so controlling GPIO (General-purpose Inputs Outputs) is super easy. In some situations, I could call myself a control freak. I wanted to be sure, it will be working flawlessly. So besides some basic logging into the local file, I set up also monitoring with service Healthchecks.io. I was &lt;a href="https://dev.to/arxeiss/cron-monitoring-for-better-sleep-3o4g"&gt;writing about it earlier&lt;/a&gt;.And for the worst case, we assigned a static IP address and set up port-forwarding to have access with SSH into the Raspberry.&lt;/p&gt;

&lt;p&gt;The python script itself was executed regularly every hour by Linux Cron. And the last feeding time was controlled programmatically. So even during power strike, fish and snail would be fed after that. You can find the full script in our &lt;a href="https://gitlab.com/profiq/public/fish-feeder"&gt;public Gitlab repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IYtO819Z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3a11uijn3muvaqwrz1e0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IYtO819Z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3a11uijn3muvaqwrz1e0.jpg" alt="Descriptions of parts on the board" width="880" height="660"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Visual feedback with non-stop stream to YouTube
&lt;/h2&gt;

&lt;p&gt;Access with SSH, logging, and monitoring might check the software behavior. But as the feeder is a piece of hardware, it is much harder to monitor. So we decided to do a stream with a web camera on YouTube. We have used ffmpeg tool and the prepared script can be found again in our &lt;a href="https://gitlab.com/profiq/public/fish-feeder"&gt;public Gitlab repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Everyones know, the developers are lazy. It is not a big surprise we kept everything to the last days. So we weren’t able to optimize streaming enough. The stream was in resolution 640x480px and 15 FPS and it still uses almost 95% of CPU time. So for safety reasons, we used another RapsberryPi just for streaming. If it would not survive, it wouldn’t affect our feeder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Oh yes, we succeeded. But what about next year's improvements?
&lt;/h2&gt;

&lt;p&gt;This article would be most likely private if the fish or the snail would die. And because you are reading it, they survived. Yaay! And we also have some baby snails. Yaay again! But there is still space for some improvements for Christmas 2022.&lt;/p&gt;

&lt;p&gt;Food for the snail is a little bit bigger and sometimes it is stuck with fish food. So nothing or just a small portion fell into the water. It would be helpful to also control the direction of moving and do some shaking when food should fall down. And for sure we need to find a better streaming setting that would allow us to watch the stream in better quality.&lt;/p&gt;

</description>
      <category>python</category>
      <category>beginners</category>
      <category>iot</category>
      <category>codenewbie</category>
    </item>
    <item>
      <title>Ignoring HTTP_PROXY environment vars in Go</title>
      <dc:creator>Pavel Kutáč</dc:creator>
      <pubDate>Tue, 11 Jan 2022 12:01:07 +0000</pubDate>
      <link>https://dev.to/arxeiss/ignoring-httpproxy-environment-vars-in-go-53bn</link>
      <guid>https://dev.to/arxeiss/ignoring-httpproxy-environment-vars-in-go-53bn</guid>
      <description>&lt;p&gt;Standard HTTP library in Go reads the environment variables &lt;code&gt;HTTP_PROXY&lt;/code&gt;, &lt;code&gt;HTTPS_PROXY&lt;/code&gt;, and &lt;code&gt;NO_PROXY&lt;/code&gt;. But in some situations, those variables are ignored. On the other hand, the developer may want to disable the reading of those variables.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;🇨🇿 V češtině si lze článek přečíst na &lt;a href="https://www.kutac.cz/co-na-srdci-to-na-blogu/go-a-ignorovani-http-proxy"&gt;kutac.cz&lt;/a&gt; &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A proxy might be useful to go over the firewall of a company network when access to some sites is blocked. But a proxy is often used to debug and inspect communication during development. For example tools like &lt;a href="https://httptoolkit.tech/"&gt;HTTP Toolkit&lt;/a&gt; creates a local proxy server.&lt;/p&gt;

&lt;p&gt;Every program in Go using &lt;code&gt;net/http&lt;/code&gt; will read &lt;code&gt;HTTP(S)_PROXY&lt;/code&gt; and &lt;code&gt;NO_PROXY&lt;/code&gt; variables and acts accordingly. But what if the developer wants to be sure, users cannot use proxy at all? The code below is an example for explaining further issues.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"HTTP_PROXY"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:8090/vm/1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ioutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&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;
  
  
  I set HTTP_PROXY and it is ignored
&lt;/h2&gt;

&lt;p&gt;In the code above the content of &lt;code&gt;HTTP_PROXY&lt;/code&gt; is printed out and then called &lt;code&gt;GET&lt;/code&gt; request. But even if the value is set correctly, it is ignored. The main reason is the request is made to &lt;strong&gt;localhost&lt;/strong&gt; which is ignored as well as &lt;strong&gt;127.x.x.x&lt;/strong&gt;. Luckily it is easy to go around this.&lt;/p&gt;

&lt;p&gt;It is enough to set custom domain in &lt;code&gt;/etc/hosts&lt;/code&gt; on Linux or &lt;code&gt;C:\Windows\System32\drivers\etc\hosts&lt;/code&gt; on Windows by adding &lt;code&gt;127.0.0.1 localserver.loc&lt;/code&gt; and then change the code above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localserver.loc:8090/vm/1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is documented behavior, but quite deeply. You can find it in source &lt;a href="https://cs.opensource.google/go/x/net/+/internal-branch.go1.17-vendor:http/httpproxy/proxy.go;l=118"&gt;opensource.google/x/net/http/httpproxy/proxy.go line 118&lt;/a&gt; and on &lt;a href="https://cs.opensource.google/go/x/net/+/internal-branch.go1.17-vendor:http/httpproxy/proxy.go;l=181"&gt;line 181&lt;/a&gt; is exact if which is responsible for that.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to disable reading PROXY environment variables
&lt;/h2&gt;

&lt;p&gt;You can use variable &lt;code&gt;NO_PROXY&lt;/code&gt; to set which URLs will never go through a proxy. You can specify all with &lt;code&gt;NO_PROXY=*&lt;/code&gt; or list URLs delimited by comma. But what to do if you as a developer wants to ignore all those variables? Then the own &lt;code&gt;Transport&lt;/code&gt; of HTTP Client must be set.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;transport&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultTransport&lt;/span&gt;
&lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transport&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Proxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Transport&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transport&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;



</description>
      <category>go</category>
      <category>todayisearched</category>
      <category>programming</category>
    </item>
    <item>
      <title>Adding events to calendar automatically from email</title>
      <dc:creator>Pavel Kutáč</dc:creator>
      <pubDate>Tue, 14 Dec 2021 12:01:14 +0000</pubDate>
      <link>https://dev.to/arxeiss/adding-events-to-calendar-automatically-from-email-1h0a</link>
      <guid>https://dev.to/arxeiss/adding-events-to-calendar-automatically-from-email-1h0a</guid>
      <description>&lt;p&gt;Many email clients can show a small calendar widget with info about event sent in email. That one can also appear in the user's calendar, but only, if is well-formatted.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;🇨🇿 V češtině si lze článek přečíst na &lt;a href="https://www.kutac.cz/pocitace-a-internety/udalosti-do-kalendare-z-emailove-pozvanky"&gt;kutac.cz&lt;/a&gt; &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Method Request and Attendee
&lt;/h2&gt;

&lt;p&gt;The email client can show info about the event based on attached iCalendar (&lt;code&gt;*.ics&lt;/code&gt;) file. However, it must satisfy a few easy requirements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Attach &lt;code&gt;*.ics&lt;/code&gt; file with all information about the event.&lt;/li&gt;
&lt;li&gt;Body of the &lt;code&gt;*.ics&lt;/code&gt; file must contain property &lt;code&gt;METHOD:REQUEST&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The Content-Type of the attachment must also contain &lt;code&gt;method=REQUEST&lt;/code&gt; part.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Attendee&lt;/code&gt; property must be part of the body of iCal file and must contain the email address of the recipient.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Below is the code in PHP for Laravel framework with &lt;a href="https://packagist.org/packages/spatie/icalendar-generator"&gt;spatie/icalendar-generator&lt;/a&gt; library. For more about sending emails in Laravel framework check &lt;a href="https://laravel.com/docs/mail"&gt;the documentation&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Carbon\Carbon&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Notifications\Messages\MailMessage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Notifications\Notification&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Spatie\IcalendarGenerator\Components\Calendar&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Spatie\IcalendarGenerator\Components\Event&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Spatie\IcalendarGenerator\Properties\TextProperty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EventCreatedNotification&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Notification&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;toMail&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;MailMessage&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$calendar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Calendar&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;productIdentifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Kutac.cz'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Event&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Email with iCal 101"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;attendee&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"attendee@gmail.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;startsAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Carbon&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2021-12-15 08:00:00"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;endsAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Carbon&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2021-12-19 17:00:00"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;fullDay&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Online - Google Meet'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="nv"&gt;$calendar&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;appendProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TextProperty&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'METHOD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'REQUEST'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;        

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MailMessage&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Invitation"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'mail.invite.created'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;attachData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$calendar&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s1"&gt;'invite.ics'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'mime'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'text/calendar; charset=UTF-8; method=REQUEST'&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;
  
  
  Issues are coming
&lt;/h2&gt;

&lt;p&gt;Everything described above is &lt;strong&gt;enough to make it work&lt;/strong&gt;. But every email client behaves differently. And that produces some issues. In the code I skipped the &lt;code&gt;-&amp;gt;organizer()&lt;/code&gt; part, and on purpose. When the organizer is specified, email clients are &lt;strong&gt;sending emails&lt;/strong&gt; when the user accepts or declines the invitation. So it can spam the organizer mailbox. Especially when the system is sending hundreds of emails.&lt;/p&gt;

&lt;p&gt;However, &lt;strong&gt;web Outlook&lt;/strong&gt; is sending those emails even if the organizer is not specified. Then the response is sent to the sender email. But what is even worse, Outlook &lt;strong&gt;will delete&lt;/strong&gt; this email after accepting or declining! This can be changed in the settings, but may confuse less skilled users. Especially when the email contains tickets or payment information.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automatic replies can be filtered out
&lt;/h3&gt;

&lt;p&gt;It is possible to filter out those automatic replies. But it is not so easy, so most likely normal filter in the email client &lt;strong&gt;will not be enough&lt;/strong&gt;. And probably some filter on the email server must be used.&lt;/p&gt;

&lt;p&gt;The reply contains the same &lt;code&gt;*.ics&lt;/code&gt; file. But instead of REQUEST method, it has &lt;code&gt;method=REPLY&lt;/code&gt; in the Content-Type and &lt;code&gt;METHOD:REPLY&lt;/code&gt; inside the body of the file.&lt;/p&gt;

&lt;h2&gt;
  
  
  More than an invitation
&lt;/h2&gt;

&lt;p&gt;GMail supports much more than just a calendar widget. It can show information about your &lt;strong&gt;upcoming flights&lt;/strong&gt;, &lt;strong&gt;hotel reservations&lt;/strong&gt;, or &lt;strong&gt;Call to action&lt;/strong&gt; button directly in the email list. But that is more complicated and you must register your application to have this available. But it is still possible and more can be found on &lt;a href="https://developers.google.com/gmail/markup"&gt;Email markup page&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;I would appreciate, if anyone solved some issues described here and would share them in comment.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>tutorial</category>
      <category>ux</category>
    </item>
    <item>
      <title>Custom providers for Terraform</title>
      <dc:creator>Pavel Kutáč</dc:creator>
      <pubDate>Tue, 16 Nov 2021 10:02:22 +0000</pubDate>
      <link>https://dev.to/arxeiss/custom-providers-for-terraform-1j6l</link>
      <guid>https://dev.to/arxeiss/custom-providers-for-terraform-1j6l</guid>
      <description>&lt;p&gt;Terraform is a tool for managing infrastructure with config files. It supports many cloud providers like GCP, AWS but also Datadog, and many others. But if you are developing your own service, which should be manageable by Terraform, you have to write your own provider.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;🇨🇿 V češtině si lze článek přečíst na &lt;a href="https://www.kutac.cz/pocitace-a-internety/vlastni-provider-do-terraformu"&gt;kutac.cz&lt;/a&gt; &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Manage infrastructure without Terraform means to log in to the Console of the provider and set up everything &lt;strong&gt;by clicking&lt;/strong&gt; in the UI. However, it is &lt;strong&gt;hard to backup&lt;/strong&gt;, &lt;strong&gt;version&lt;/strong&gt;, and &lt;strong&gt;replicate&lt;/strong&gt; for other environments like staging. And that is the reason why we have Terraform. If you don't know much about Terraform, watch &lt;a href="https://www.youtube.com/watch?v=tomUWcQ0P3k"&gt;Terraform in 100s&lt;/a&gt; video before we dive into &lt;strong&gt;writing own providers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KRqHBUYO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4cc2ymcp1sab8q20hz9m.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KRqHBUYO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4cc2ymcp1sab8q20hz9m.jpg" alt="Basic schema of Terraform" width="830" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Behind the scenes
&lt;/h2&gt;

&lt;p&gt;Before writing own provider, let's look at how Terraform works in the background. Terraform first reads all &lt;code&gt;*.tf&lt;/code&gt; files and prepares &lt;strong&gt;dependency graph&lt;/strong&gt; - the execution &lt;strong&gt;plan&lt;/strong&gt;. Based on the plan Terraform then can perform &lt;strong&gt;CRUD&lt;/strong&gt; operations. Those methods must be implemented in the provider which is responsible for calling &lt;strong&gt;backend API&lt;/strong&gt;. So Terraform does not care if you use REST, gRPC, or SOAP gRPC API.&lt;/p&gt;

&lt;p&gt;Terraform core just parses the file and performs the plan by calling the provider's CRUD functions. Theoretically, someone could write blog posts with Terraform.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yZahbpGl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y4jrfbnieum70m5p6quf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yZahbpGl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y4jrfbnieum70m5p6quf.png" alt="Schema of communication between Terraform Core and backend API" width="880" height="156"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo app
&lt;/h2&gt;

&lt;p&gt;I created a sample project which contains a simple &lt;strong&gt;HTTP server&lt;/strong&gt; with REST API. And also a &lt;strong&gt;custom provider&lt;/strong&gt; which is communicating with that HTTP server. So you can easily play with the provider and Terraform. Full code can be found on my GitHub &lt;a href="https://github.com/arxeiss/sample-terraform-provider"&gt;github.com/arxeiss/sample-terraform-provider&lt;/a&gt;&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;# Start the HTTP server&lt;/span&gt;
make start_server 

&lt;span class="c"&gt;# Build Terraform provider&lt;/span&gt;
make terraform_build 

&lt;span class="c"&gt;# All *.tf files are in config subfolder&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;provider/config 
terraform plan &lt;span class="c"&gt;# to show plan which will be executed&lt;/span&gt;
terraform apply &lt;span class="c"&gt;# to execute the plan&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>go</category>
      <category>terraform</category>
      <category>devops</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Cron monitoring for better sleep</title>
      <dc:creator>Pavel Kutáč</dc:creator>
      <pubDate>Tue, 19 Oct 2021 09:01:20 +0000</pubDate>
      <link>https://dev.to/arxeiss/cron-monitoring-for-better-sleep-3o4g</link>
      <guid>https://dev.to/arxeiss/cron-monitoring-for-better-sleep-3o4g</guid>
      <description>&lt;p&gt;Regular execution of jobs with Cron is common in almost every system. However, discovering the job stops being executed might be difficult and can take some time. So it is important to monitor it.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;🇨🇿 V češtině si lze článek přečíst na &lt;a href="https://www.kutac.cz/pocitace-a-internety/monitoring-cronu-pro-klidnejsi-spani"&gt;kutac.cz&lt;/a&gt; &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Bugs, which are happening just before the eyes of the user, someone will report eventually. But when background tasks are stopped, no one might notice it for even months. So monitoring is essential. And it might not be even so expensive or hard.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Everyone probably heard some story of non-working DB backups. Which was discovered only, when the backup was really needed. And one simple monitoring would save it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Healthchecks.io - Dead's man switch
&lt;/h1&gt;

&lt;p&gt;In the previous article, I wrote about &lt;a href="https://dev.to/arxeiss/cron-job-org-free-cron-service-2ge"&gt;Cron-job service&lt;/a&gt; which might be good to trigger cron tasks. However, what happens, when the response code is 200 but nothing is executed? This is really hard to discover. So services like &lt;a href="https://healthchecks.io/"&gt;Healthchecks.io&lt;/a&gt; are here to help. And with a quite &lt;strong&gt;generous free tier&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Service is working on Dead's man switch principle. It is required to notify the service in set intervals, otherwise notifications are being sent. For example to email, SMS, phone call, Slack, Discord, Teams, Trello, WhatsApp, or one of another 16 services.&lt;/p&gt;

&lt;p&gt;There are 2 check types. One is called &lt;strong&gt;Simple&lt;/strong&gt; which accepts the interval in which the system is expecting pings. The second one is &lt;strong&gt;Cron&lt;/strong&gt;, which supports basic Cron syntax to define times when to run. Both support also &lt;strong&gt;Grace time&lt;/strong&gt; which defines the maximum possible delay.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qBF8_1Ph--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/747e6lumk6o3t2axgrlq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qBF8_1Ph--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/747e6lumk6o3t2axgrlq.png" alt="Example of Healthecks.io checks"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Success Ping, Start, Fail, and Exit code
&lt;/h2&gt;

&lt;p&gt;Only &lt;strong&gt;success Ping&lt;/strong&gt; is required as a minimal starting point. By sending Ping you are telling that now the task was processed. If the service does not receive a ping at a given time or interval, notifications are sent. Endpoint &lt;strong&gt;Start&lt;/strong&gt; is notifying that the task just started. Now the service is expecting &lt;strong&gt;Ping&lt;/strong&gt; in defined limit since Start. This is good for detecting endless loops or stuck code.&lt;/p&gt;

&lt;p&gt;Endpoints &lt;strong&gt;Fail&lt;/strong&gt; and &lt;strong&gt;Exit code&lt;/strong&gt; is there to notify about failure. With Exit code, you can specify the numeric exit code of the job.&lt;/p&gt;

&lt;p&gt;It is possible to attach a &lt;strong&gt;log message&lt;/strong&gt; with a size up to &lt;strong&gt;10 kB&lt;/strong&gt; to the requests. For more info see &lt;a href="https://healthchecks.io/docs/"&gt;the documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  HTTP requests and retry
&lt;/h2&gt;

&lt;p&gt;As documentation states, HTTP pings are not really reliable.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Sending monitoring signals over the public internet is inherently unreliable. HTTP requests can sometimes take excessively long or fail completely for a variety of reasons.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is required to set reasonable timeouts and a retry policy. Here is a simplified helper class for the Laravel framework below. It would be better to use the Retry plugin, but simple &lt;code&gt;for&lt;/code&gt; might be enough here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Services&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;GuzzleHttp\Client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Log&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HealthChecksService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt; &lt;span class="s1"&gt;'base_uri'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'healthchecks.api_endpoint'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;makeRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$checkName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'timeout'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'connect_timeout'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'body'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="nv"&gt;$exception&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'POST'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nb"&gt;rtrim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$action&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;$options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nv"&gt;$exception&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nc"&gt;Throwable&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$exception&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nb"&gt;usleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nb"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;500000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'Cannot perform HealthChecks request'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'checkName'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$checkName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'action'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'exception'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$exception&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'retry'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&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;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$checkName&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;makeRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'start'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$checkName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$checkName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;makeRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$checkName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$log&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;exitStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$checkName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$exitStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;makeRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$exitStatus&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$checkName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$log&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;



</description>
      <category>monitoring</category>
      <category>devops</category>
      <category>tutorial</category>
      <category>laravel</category>
    </item>
  </channel>
</rss>
