<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Alexey Melezhik</title>
    <description>The latest articles on Forem by Alexey Melezhik (@melezhik).</description>
    <link>https://forem.com/melezhik</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%2F36281%2F1de54bc5-85b1-4537-8ae5-11e2d484c529.jpeg</url>
      <title>Forem: Alexey Melezhik</title>
      <link>https://forem.com/melezhik</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/melezhik"/>
    <language>en</language>
    <item>
      <title>Dead Simple CI Introduction</title>
      <dc:creator>Alexey Melezhik</dc:creator>
      <pubDate>Sat, 14 Feb 2026 13:11:55 +0000</pubDate>
      <link>https://forem.com/melezhik/dead-simple-ci-introduction-1jh6</link>
      <guid>https://forem.com/melezhik/dead-simple-ci-introduction-1jh6</guid>
      <description>&lt;p&gt;&lt;a href="http://deadsimpleci.sparrowhub.io/doc/README" rel="noopener noreferrer"&gt;Dsci&lt;/a&gt; is a brand new kid on the cicd area. It allows use of general programming languages to create ci scenarios.&lt;/p&gt;

&lt;p&gt;——-&lt;/p&gt;

&lt;p&gt;Consider some imaginary case of building and deploying an application as docker container. So, logically we have two main stages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;build docker image from source code &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;deploy docker image using docker pull and docker run commands&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s see how dsci could tackle such a scenario …&lt;/p&gt;

&lt;p&gt;——-&lt;/p&gt;

&lt;h1&gt;
  
  
  Pipeline file
&lt;/h1&gt;

&lt;p&gt;The main concept we start with is so called ‘pipeline file’.&lt;/p&gt;

&lt;p&gt;It’s just yaml file containing a list of jobs to be performed. Every job is executed on isolated docker container, execution logic is always sequential - one job, by another.&lt;/p&gt;

&lt;p&gt;This pattern yet simple however covers the majority of use cases:&lt;/p&gt;

&lt;p&gt;jobs.yaml&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="pi"&gt;-&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;build&lt;/span&gt;
     &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build/&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt;
     &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
     &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy/&lt;/span&gt;    
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thus, every element of job list is a job, where job has some unique identifier and path to location of job file.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conditional job logic
&lt;/h1&gt;

&lt;p&gt;Usually there are some conditions when and what ci scripts are triggered. Those conditions may be based on different criteria, but usually they depend on branch names.&lt;/p&gt;

&lt;p&gt;Dsci utilizes a special syntax to define those rules. Let’s say we only want to deploy if source branch is main, and perform build for all branches:&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="pi"&gt;-&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;build&lt;/span&gt;
     &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build/&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt;
     &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
     &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy/&lt;/span&gt; 
     &lt;span class="na"&gt;only&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&amp;lt;ref&amp;gt; eq "refs/heads/main"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read more on job conditions on dsci documentation - &lt;a href="https://github.com/melezhik/DSCI/blob/main/job-cond.md" rel="noopener noreferrer"&gt;https://github.com/melezhik/DSCI/blob/main/job-cond.md&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Passing parameters to job
&lt;/h1&gt;

&lt;p&gt;To pass parameters to job, just use &lt;code&gt;params:&lt;/code&gt; key:&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="pi"&gt;-&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;build&lt;/span&gt;
     &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build/&lt;/span&gt;
     &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bar&lt;/span&gt;
       &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.1.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&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;deploy&lt;/span&gt;
     &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy/&lt;/span&gt;    
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those parameters could be thought as overrides for default ones, however dsci allows more then that - pass results ( states ) from one job to another, see later.&lt;/p&gt;

&lt;h1&gt;
  
  
  Job file
&lt;/h1&gt;

&lt;p&gt;The next important building block of the hierarchy is so called “job file” defining main job logic.&lt;/p&gt;

&lt;p&gt;Job file needs to be named depending on language of choice, in our example we use Bash as it usually is enough for many use cases. But using other languages is also possible. If one needs more flexibility they may chose Python or Golang&lt;/p&gt;

&lt;p&gt;build/task.bash&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="nv"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;config version&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;tag_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s&lt;span class="si"&gt;)&lt;/span&gt;.&lt;span class="nv"&gt;$version&lt;/span&gt;
docker build &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; app:&lt;span class="nv"&gt;$tag_version&lt;/span&gt;
docker push repo/app:&lt;span class="nv"&gt;$tag_version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this simple example our job file is just a single task job. However if there is a need to split complex job into several tasks they may do so by using job.$ext file approach. This file needs to be named according language of choice. &lt;/p&gt;

&lt;p&gt;Let’s say we have three tasks - configure, build and test - incorporated into build job, we may run them one by one like this:&lt;/p&gt;

&lt;p&gt;build/job.py&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/bin/python
&lt;/span&gt;&lt;span class="nf"&gt;run_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;run_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;run_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if we put those tasks under build/tasks/ directory like this:&lt;/p&gt;

&lt;p&gt;build/tasks/configure/task.bash&lt;br&gt;
build/tasks/build/tasks.bash&lt;br&gt;
build/tasks/tests/task.bash&lt;/p&gt;

&lt;p&gt;In this case we have modular setup of our ci job.  &lt;/p&gt;

&lt;p&gt;The neat thing about this DSL, dsci provides the same SDK for all supported programming languages.&lt;/p&gt;

&lt;p&gt;Read more about jobs and tasks on dsci documentation web site:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="http://deadsimpleci.sparrowhub.io/doc/task" rel="noopener noreferrer"&gt;http://deadsimpleci.sparrowhub.io/doc/task&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="http://deadsimpleci.sparrowhub.io/doc/job" rel="noopener noreferrer"&gt;http://deadsimpleci.sparrowhub.io/doc/job&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Passing results between jobs
&lt;/h1&gt;

&lt;p&gt;Let’s make our last job example more realistic and return docker image tag dynamically created by build job back to deploy job. We may want to do so deploy jobs know what tag to pull before deploy. All we need is to add extra line to save the job state into internal dsci cache:&lt;/p&gt;

&lt;p&gt;build/task.bash&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="nv"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;config version&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;tag_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s&lt;span class="si"&gt;)&lt;/span&gt;.&lt;span class="nv"&gt;$version&lt;/span&gt;
docker build &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; app:&lt;span class="nv"&gt;$tag_version&lt;/span&gt;
docker push repo/app:&lt;span class="nv"&gt;$tag_version&lt;/span&gt;
update_state tag &lt;span class="nv"&gt;$tag_version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;update_state() is very handy function allowing to pass states between different tasks and jobs. It’s implemented for all supported languages.&lt;/p&gt;

&lt;p&gt;To pick up tag name in deploy job we can use already mentioned config() function:&lt;/p&gt;

&lt;p&gt;deploy/task.bash&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="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;
&lt;span class="nv"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;config tag&lt;span class="si"&gt;)&lt;/span&gt;
docker pull repo/app@&lt;span class="nv"&gt;$tag&lt;/span&gt;
docker stop &lt;span class="nt"&gt;-t&lt;/span&gt; 1 container &lt;span class="o"&gt;||&lt;/span&gt; :
docker run &lt;span class="nt"&gt;-rm&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; container &lt;span class="nt"&gt;-td&lt;/span&gt; repo/app@&lt;span class="nv"&gt;$tag&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read more about job and tasks states on dsci documentation ( the links above ^^ )&lt;/p&gt;

&lt;h1&gt;
  
  
  Secrets
&lt;/h1&gt;

&lt;p&gt;Let’s modify build job example with pushing image to docker registry. To do a push we need to authenticate against a docker registry first. Dsci enables simple way to pass secrets to pipelines.&lt;/p&gt;

&lt;p&gt;This time let’s rewrite our job file on Python for convenience:&lt;/p&gt;

&lt;p&gt;build/job.py&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/bin/python
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="n"&gt;password&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="nf"&gt;run_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then modify build task to handle password parameter:&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="nv"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;config version&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;tag_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s&lt;span class="si"&gt;)&lt;/span&gt;.&lt;span class="nv"&gt;$version&lt;/span&gt;
docker build &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; app:&lt;span class="nv"&gt;$tag_version&lt;/span&gt;
update_state tag &lt;span class="nv"&gt;$tag_version&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$password&lt;/span&gt; | docker login &lt;span class="nt"&gt;--username&lt;/span&gt; your-username &lt;span class="nt"&gt;--password-stdin&lt;/span&gt;

docker push repo/app:&lt;span class="nv"&gt;$tag_version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we can see secrets passed into pipelines as environment variables.&lt;/p&gt;

&lt;p&gt;And unlike other task and jobs parameters they never saved in job reports or cache files.  &lt;/p&gt;

&lt;p&gt;—-&lt;/p&gt;

&lt;h1&gt;
  
  
  Deployment on localhost
&lt;/h1&gt;

&lt;p&gt;By default dsci pipelines run inside some docker container, this fits situation when one needs run purely ci code - for example build and run some unit tests. CD part comes to play when build artifacts are ready for deploy. &lt;/p&gt;

&lt;p&gt;Dsci allows switch to deployment environment by using &lt;code&gt;localhost&lt;/code&gt; switcher:&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="pi"&gt;-&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;build&lt;/span&gt;
     &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build/&lt;/span&gt;
     &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bar&lt;/span&gt;
       &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.1.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&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;deploy&lt;/span&gt;
     &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy/&lt;/span&gt;
     &lt;span class="na"&gt;localhost&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In that case deployment occurs on VM running dsci orchestrator and which allows to restart docker container with new image version right on VM&lt;/p&gt;

&lt;p&gt;——-&lt;/p&gt;

&lt;p&gt;That is it. &lt;/p&gt;

&lt;p&gt;This simple but real life example shows how easy and one can write cicd pipelines using dsci framework and how flexible it is.&lt;/p&gt;

&lt;p&gt;Hopefully you like it. &lt;/p&gt;

&lt;p&gt;—-&lt;/p&gt;

&lt;p&gt;For comprehensive documentation and more information please visit dsci web site - &lt;a href="http://deadsimpleci.sparrowhub.io/doc/README" rel="noopener noreferrer"&gt;http://deadsimpleci.sparrowhub.io/doc/README&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cicd</category>
      <category>pipelines</category>
      <category>forgejo</category>
    </item>
    <item>
      <title>DTAP - super simple testing protocol for infrastructure audit</title>
      <dc:creator>Alexey Melezhik</dc:creator>
      <pubDate>Thu, 08 Jan 2026 12:54:15 +0000</pubDate>
      <link>https://forem.com/melezhik/dtap-super-simple-testing-protocol-for-infrastructure-audit-2l45</link>
      <guid>https://forem.com/melezhik/dtap-super-simple-testing-protocol-for-infrastructure-audit-2l45</guid>
      <description>&lt;p&gt;&lt;a href="http://doubletap.sparrowhub.io" rel="noopener noreferrer"&gt;DTAP&lt;/a&gt; is a new testing protocol allowing to test infrastructure with just a Bash scripts. Here is a quick example, let's test that &lt;code&gt;/etc/dhcp/&lt;/code&gt; directory and &lt;code&gt;/etc/dhcpcd.conf&lt;/code&gt; file exists:&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;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;session&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;ls&lt;/span&gt; /etc/dhcp/ 2&amp;gt;&amp;amp;1 | dtap &lt;span class="nt"&gt;--box&lt;/span&gt; - &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--session&lt;/span&gt; &lt;span class="nv"&gt;$session&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--params&lt;/span&gt; &lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/dhcp/ &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--check&lt;/span&gt; path-ok &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--desc&lt;/span&gt; &lt;span class="s2"&gt;"dhcp/ dir"&lt;/span&gt;

&lt;span class="nb"&gt;ls&lt;/span&gt; /etc/dhcpcd.conf  2&amp;gt;&amp;amp;1 | dtap &lt;span class="nt"&gt;--box&lt;/span&gt; - &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--session&lt;/span&gt; &lt;span class="nv"&gt;$session&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--params&lt;/span&gt; &lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/dhcpcd.conf &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--check&lt;/span&gt; path-ok &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--desc&lt;/span&gt; &lt;span class="s2"&gt;"dhcpcd.conf file"&lt;/span&gt;

dtap  &lt;span class="nt"&gt;--report&lt;/span&gt;  &lt;span class="nt"&gt;--session&lt;/span&gt; &lt;span class="nv"&gt;$session&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result will be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DTAP report
session: 1767875428
===
dhcp/ dir ...... OK
dhcpcd.conf file ...... OK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plain and simple&lt;/p&gt;




&lt;p&gt;As one can see test scripts are just plain Bash commands, no fancy YAML or even high level programming languages.&lt;/p&gt;

&lt;p&gt;Also DTAP follows WYSIWYG principle when we get exactly what we see, in a sense this is something we would do trying to check existence  of the mentioned directory and file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ls /etc/dhcp/
ls /etc/dhcpcd.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if any errors occurr we will get exactly what we are asking for - output of &lt;code&gt;ls&lt;/code&gt; command which most of the Linux users probably are familiar with:&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="o"&gt;!&lt;/span&gt;/bin/bash
&lt;span class="nb"&gt;ls&lt;/span&gt;  /etc/does-not-exist 2&amp;gt;&amp;amp;1 | dtap &lt;span class="nt"&gt;--box&lt;/span&gt; - &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--session&lt;/span&gt; &lt;span class="nv"&gt;$session&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--params&lt;/span&gt; &lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/does-not-exist &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--check&lt;/span&gt; path-ok &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--desc&lt;/span&gt; &lt;span class="s2"&gt;"/etc/does-not-exist dir"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DTAP report
session: 1767875841
===
/etc/does-not-exist dir ...... FAIL
[report]
15:37:22 :: [sparrowtask] - run sparrow task .@path=/etc/does-not-exist
15:37:22 :: [sparrowtask] - run [.], thing: .@path=/etc/does-not-exist
[task run: task.bash - .]
[task stdout]
15:37:23 :: ls: cannot access '/etc/does-not-exist': No such file or directory
[task check]
stdout match &amp;lt;^^ "/etc/does-not-exist"  $$&amp;gt; False
=================
TASK CHECK FAIL
2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;How this works?&lt;/p&gt;

&lt;p&gt;There are two essentials primitives in DTAP:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;boxes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;and - checks&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Box is an abstraction for everything we want to test - from web server to messages in syslog files. Boxes produces some output to be checked against check rules ( aka checks ). In the previous examples - boxe is just &lt;code&gt;ls&lt;/code&gt; command which output redirected to a certain check rule via &lt;code&gt;--box -&lt;/code&gt; notation ( meaning read box output from STDIN ). DTAP comes with some predefined boxes user can use them without writing any piece of code, but most of the time boxes - are something a user would write as a chain of Bash commands something likes 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;#!/bin/bash&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt; 
  2&amp;gt;&amp;amp;1
  cmd1&lt;span class="p"&gt;;&lt;/span&gt;
  cmd2&lt;span class="p"&gt;;&lt;/span&gt;
  cmd3&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c"&gt;# ...&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt; | tap &lt;span class="nt"&gt;--box&lt;/span&gt; -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or with a single 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;#!/bin/bash&lt;/span&gt;
&lt;span class="nb"&gt;.&lt;/span&gt; /some/box/script.sh | tap &lt;span class="nt"&gt;--box&lt;/span&gt; -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Checks are rules written on formal &lt;a href="https://github.com/melezhik/Sparrow6/blob/master/documentation/taskchecks.md" rel="noopener noreferrer"&gt;DSL&lt;/a&gt; and executed remotely on DTAP server, so users don't need to install anything, only small &lt;a href="http://doubletap.sparrowhub.io/install" rel="noopener noreferrer"&gt;tap binary&lt;/a&gt; written on golang that interacts with a server send output from boxes to a server and get results back. Checks DSL is based on regular expressions and is super flexible, allowing many things to do including extension by using many general programming languages. &lt;/p&gt;

&lt;p&gt;As an example if you look inside &lt;a href="https://github.com/melezhik/doubletap/blob/main/checks/path-ok/task.check" rel="noopener noreferrer"&gt;path-ok&lt;/a&gt; check that verifies file/directory existence you'll see something 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;generator: &amp;lt;&amp;lt;OK
!bash
echo "regexp: ^^ \"$(config path)\"  \$\$"
OK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;To list available checks just run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tap &lt;span class="nt"&gt;--check_list&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Follow double tap web site &lt;a href="http://doubletap.sparrowhub.io/checks" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; to get details on using available checks.&lt;/p&gt;

&lt;p&gt;It's easy to create new checks and add them to DTAP distribution, if you are instead please let me know. There is quick start introduction  into check DSL could be found &lt;a href="https://git.resf.org/testing/sparrow_task_check_quick_start/src/branch/main" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Conclusion.&lt;/p&gt;

&lt;p&gt;DTAP is a new kid on the block, that allows to test infrastructure with using just a Bash yet very flexible and powerful. I encourage you to play with it, you can start with &lt;a href="http://doubletap.sparrowhub.io/install" rel="noopener noreferrer"&gt;installation&lt;/a&gt; guide&lt;/p&gt;




&lt;p&gt;Thanks for reading &lt;/p&gt;

</description>
      <category>testing</category>
      <category>inspec</category>
      <category>infrastructure</category>
      <category>devops</category>
    </item>
    <item>
      <title>raku-sparrow6 - swiss army knife for alpine linux</title>
      <dc:creator>Alexey Melezhik</dc:creator>
      <pubDate>Wed, 23 Jul 2025 14:30:07 +0000</pubDate>
      <link>https://forem.com/melezhik/raku-sparrow6-swiss-army-knife-for-alpine-linux-2ngh</link>
      <guid>https://forem.com/melezhik/raku-sparrow6-swiss-army-knife-for-alpine-linux-2ngh</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/melezhik/Sparrow6" rel="noopener noreferrer"&gt;Sparrow&lt;/a&gt; is a Rakulang based framework that allows to automate routine tasks, the beauty of it - one can use prepacked plugins available from public Sparrow repository - &lt;a href="https://sparrowhub.io" rel="noopener noreferrer"&gt;https://sparrowhub.io&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Install Sparrow framework
&lt;/h2&gt;

&lt;p&gt;Sparrow is installed as native Alpine package:&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="nb"&gt;sudo &lt;/span&gt;apk add &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="nt"&gt;--wait&lt;/span&gt; 120 &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--repository&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://dl-cdn.alpinelinux.org/alpine/edge/community &lt;span class="se"&gt;\&lt;/span&gt;
raku-sparrow6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set up plugins repository&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="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SP6_REPO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://sparrowhub.io/repo
s6 &lt;span class="nt"&gt;--index-update&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Search alpine plugins
&lt;/h2&gt;

&lt;p&gt;Sparrowhub has a lot of plugins, to limit search to Alpine related plugins:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;s6 &lt;span class="nt"&gt;--search&lt;/span&gt; alpine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are now not too many of them for Alpine, eventually I am going to add more, put in comments what sort of plugins one would like to see&lt;/p&gt;

&lt;h2&gt;
  
  
  Run plugin
&lt;/h2&gt;

&lt;p&gt;To run plugin, use Sparrow cli, you can also pass parameters to a plugin. For example, to validate APKBUILD file one can use &lt;code&gt;apkbuild-strict&lt;/code&gt; plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;s6 &lt;span class="nt"&gt;--plg-run&lt;/span&gt; apkbuild-strict@path&lt;span class="o"&gt;=&lt;/span&gt;/path/to/apkbuild/file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That way Sparrow plugins could be run from terminal or get inlined into shell scripts&lt;/p&gt;

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

&lt;p&gt;It'd be great to hear feedback from (Alpine) users, what sort of tasks could be implemented by Sparrow&lt;/p&gt;

&lt;h2&gt;
  
  
  Credits
&lt;/h2&gt;

&lt;p&gt;Kudos to &lt;a href="https://pkgs.alpinelinux.org/packages?name=&amp;amp;branch=edge&amp;amp;repo=&amp;amp;arch=&amp;amp;maintainer=Celeste" rel="noopener noreferrer"&gt;Celeste&lt;/a&gt; - Alpine maintainer for raku-sparrow6 Alpine package creation &lt;/p&gt;

</description>
      <category>sparrow</category>
      <category>rakulang</category>
      <category>alpine</category>
      <category>linux</category>
    </item>
    <item>
      <title>Validating configuration files with Raku and Sparrow Task::Check DSL</title>
      <dc:creator>Alexey Melezhik</dc:creator>
      <pubDate>Thu, 30 Jan 2025 08:53:50 +0000</pubDate>
      <link>https://forem.com/melezhik/validating-config-files-with-raku-and-sparrow-taskcheck-dsl-1ekp</link>
      <guid>https://forem.com/melezhik/validating-config-files-with-raku-and-sparrow-taskcheck-dsl-1ekp</guid>
      <description>&lt;p&gt;In real world we have a lot of pure structured or structured in hard to parse formats configuration files:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/etc/default/nginx&lt;/code&gt;, &lt;code&gt;/etc/default/grub&lt;/code&gt;, &lt;code&gt;logrotate.conf&lt;/code&gt;, &lt;code&gt;sysctl.conf&lt;/code&gt; - to name a few. &lt;/p&gt;

&lt;p&gt;In configuration management it's not only important to maintain specific configurations but also validate those changes to make it sure we don't break things.&lt;/p&gt;

&lt;p&gt;Sparrow - is an automation tool written on Raku that allow to make such a validation by leveraging Task::Check DSL rules which underneath are just Raku regular expressions.&lt;/p&gt;

&lt;p&gt;In the rest of this post we take a look at real life example.&lt;/p&gt;




&lt;h1&gt;
  
  
  Grub
&lt;/h1&gt;

&lt;p&gt;Consider linux GRUB boot loader file &lt;code&gt;/etc/default/grub&lt;/code&gt; with following  configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GRUB_DEFAULT=0
#GRUB_HIDDEN_TIMEOUT=0
GRUB_HIDDEN_TIMEOUT_QUIET=true
GRUB_TIMEOUT=2
GRUB_DISTRIBUTOR=`lsb_release -i -s 2&amp;gt; /dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT="rootfstype=ext4 quiet splash acpi_osi="
GRUB_CMDLINE_LINUX=""
GRUB_ENABLE_CRYPTODISK=true

# Uncomment to enable BadRAM filtering, modify to suit your needs
# This works with Linux (no patch required) and with any kernel that obtains
# the memory map information from GRUB (GNU Mach, kernel of FreeBSD ...)
#GRUB_BADRAM="0x01234567,0xfefefefe,0x89abcdef,0xefefefef"

# Uncomment to disable graphical terminal (grub-pc only)
#GRUB_TERMINAL=console

# The resolution used on graphical terminal
# note that you can use only modes which your graphic card supports via VBE
# you can see them in real GRUB with the command `vbeinfo'
#GRUB_GFXMODE=640x480

# Uncomment if you don't want GRUB to pass "root=UUID=xxx" parameter to Linux
#GRUB_DISABLE_LINUX_UUID=true

# Uncomment to disable generation of recovery mode menu entries
#GRUB_DISABLE_RECOVERY="true"

# Uncomment to get a beep at grub start
#GRUB_INIT_TUNE="480 440 1"

GRUB_DISABLE_OS_PROBER="true"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Though configuration format is quite simple, based on VAR=value semantics, it still allows some freedom in specification. &lt;/p&gt;

&lt;p&gt;For example to enable variables we could use one of many forms - &lt;code&gt;yes/"yes"&lt;/code&gt;, &lt;code&gt;true/"true"&lt;/code&gt; and just &lt;code&gt;1&lt;/code&gt;. Another challenge is to check that some variable are disabled - they might be just commented or “switched off” via negation form, e.g. VAR=0,false,""&lt;/p&gt;




&lt;p&gt;As rigorous operations we want to validate a couple of things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Boot on crypted disks is supported by setting GRUB_ENABLE_CRYPTODISK to one of the values: &lt;code&gt;1,yes,true&lt;/code&gt; and optional quoted form - &lt;code&gt;"yes","true"&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;OS prober capability is disabled or in other words GRUB_DISABLE_OS_PROBER is not set by using any of forms - &lt;code&gt;1,yes,true,"yes","true"&lt;/code&gt; &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before go parsing let's create simple Raku program (Sparrow task), that dumps out configuration removing empty and commented lines, the whole output will be placed inside &lt;code&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/code&gt; markers for visibility:&lt;/p&gt;

&lt;p&gt;task.raku&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!raku&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;config&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;validate config {&lt;/span&gt;&lt;span class="si"&gt;$path&lt;/span&gt;&lt;span class="s2"&gt;} ...&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;

&lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;@out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;lines&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="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="nb"&gt;chomp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;next&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;~~&lt;/span&gt; &lt;span class="sr"&gt;/\S+/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nv"&gt;@out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;push&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="k"&gt;unless&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;~~&lt;/span&gt; &lt;span class="sr"&gt;/^ \s* '#'/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="nb"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;@out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;join&lt;/span&gt;&lt;span class="p"&gt;("&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="p"&gt;");&lt;/span&gt;
&lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Now let's write verification scenario by creating rules in Sparrow::Task::Check DSL format:&lt;/p&gt;

&lt;p&gt;task.check&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;note: validate config

between: {'&amp;gt;&amp;gt;&amp;gt;'}  {'&amp;lt;&amp;lt;&amp;lt;'}

  note: allow to boot on crypted disks
  regexp: ^^ \s* "GRUB_ENABLE_CRYPTODISK=" (&amp;lt;[y 1 true]&amp;gt;  | '"true"' | '"yes"' )

  note: never allow to enable os prober
  !regexp: ^^ \s* "GRUB_DISABLE_OS_PROBER=" (&amp;lt;[y 1 true]&amp;gt; | '"true"' | '"yes"' )

end:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The DSL code has there major parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;between: {'&amp;gt;&amp;gt;&amp;gt;'}  {'&amp;lt;&amp;lt;&amp;lt;'}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Setting a context for a search in between &lt;code&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt; ... &lt;code&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/code&gt; delimiter, this is an example of so called &lt;em&gt;search context modifiers&lt;/em&gt;, in this case - Range modifier. &lt;/p&gt;

&lt;p&gt;In other words this is precaution that we only search within specific boundaries of text output&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; &lt;code&gt;regexp: ^^ \s* "GRUB_ENABLE_CRYPTODISK=" (&amp;lt;[y 1 true]&amp;gt;  | '"true"' | '"yes"' )&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Verifying that we have &lt;code&gt;GRUB_ENABLE_CRYPTODISK&lt;/code&gt; set to one of values (y,1,true,"true","yes"). &lt;/p&gt;

&lt;p&gt;&lt;code&gt;regexp:&lt;/code&gt; marker means Raku regular expression is used for validation.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;note: never allow to enable os prober
!regexp: ^^ \s* "GRUB_DISABLE_OS_PROBER=" (&amp;lt;[y 1 true]&amp;gt; | '"true"' | '"yes"' )&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Verifying we DON'T(*) have &lt;code&gt;GRUB_ENABLE_CRYPTODISK&lt;/code&gt; set to one of values (y,1,true,"true","yes"), in other words that GRUB_ENABLE_CRYPTODISK is disabled. &lt;/p&gt;

&lt;p&gt;(*) &lt;code&gt;!&lt;/code&gt; sign before &lt;code&gt;regexp:&lt;/code&gt; marker means negation logic.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Note:&lt;/code&gt; expressions are human readable comments that are showed up in a  report to help us correlate every check to business logic&lt;/p&gt;

&lt;h1&gt;
  
  
  Test report
&lt;/h1&gt;

&lt;p&gt;Now let's run our script and get a result of test. As this is Sparrow scenario, we use &lt;code&gt;s6&lt;/code&gt; - Sparrow CLI runner:&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;#!bash&lt;/span&gt;
s6 &lt;span class="nt"&gt;--task-run&lt;/span&gt; .@path&lt;span class="o"&gt;=&lt;/span&gt;/etc/default/grub
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;14:09:20 :: [sparrowtask] - run sparrow task .@path=/etc/default/grub
14:09:20 :: [sparrowtask] - run thing .
[task run: task.raku - .]
[task stdout]
14:09:21 :: validate config examples/grub ...
14:09:21 :: &amp;gt;&amp;gt;&amp;gt;
14:09:21 :: GRUB_CMDLINE_LINUX=""
14:09:21 :: GRUB_CMDLINE_LINUX_DEFAULT="rootfstype=ext4 quiet splash acpi_osi="
14:09:21 :: GRUB_DEFAULT=0
14:09:21 :: GRUB_DISABLE_OS_PROBER="true"
14:09:21 :: GRUB_DISTRIBUTOR=`lsb_release -i -s 2&amp;gt; /dev/null || echo Debian`
14:09:21 :: GRUB_ENABLE_CRYPTODISK=true
14:09:21 :: GRUB_HIDDEN_TIMEOUT_QUIET=true
14:09:21 :: GRUB_TIMEOUT=2
14:09:21 :: &amp;lt;&amp;lt;&amp;lt;
[task check]
# validate config
# allow to boot on crypted disks
stdout match (r) &amp;lt;^^ \s* "GRUB_ENABLE_CRYPTODISK=" (&amp;lt;[y 1 true]&amp;gt;  | '"true"' | '"yes"' )&amp;gt; True
# never allow to enable os prober
stdout match (r) &amp;lt;!^^ \s* "GRUB_DISABLE_OS_PROBER=" (&amp;lt;[y 1 true]&amp;gt; | '"true"' | '"yes"' )&amp;gt; False
=================
TASK CHECK FAIL

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;em&gt;PS for colorful report please click &lt;a href="https://pasteboard.co/grlUKxSkrGYf.jpg" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Expectedly we have following results:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GRUB_ENABLE_CRYPTODISK check passed&lt;/li&gt;
&lt;li&gt;GRUB_DISABLE_OS_PROBER fails &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pretty impressive results considering such a little amount of code has been written!&lt;/p&gt;




&lt;p&gt;Sparrow &lt;a href="https://github.com/melezhik/Sparrow6/blob/master/documentation/taskchecks.md" rel="noopener noreferrer"&gt;Task::Check&lt;/a&gt;s is comprehensive and super flexible tool allowing one to write validation scenarios for arbitrary data formats and complexity, check out documentation pages to know more.&lt;/p&gt;

&lt;p&gt;That is it. Thanks for reading &lt;/p&gt;

</description>
      <category>rakulang</category>
      <category>linux</category>
      <category>grub</category>
      <category>data</category>
    </item>
    <item>
      <title>Useful terminal plugins to build golang code</title>
      <dc:creator>Alexey Melezhik</dc:creator>
      <pubDate>Fri, 24 Jan 2025 09:16:01 +0000</pubDate>
      <link>https://forem.com/melezhik/useful-terminal-plugins-to-build-golang-code-gj0</link>
      <guid>https://forem.com/melezhik/useful-terminal-plugins-to-build-golang-code-gj0</guid>
      <description>&lt;p&gt;Recently, I've released 2 new Sparrow plugins to help build Golang code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://sparrowhub.io/plugin/go-build/0.000001" rel="noopener noreferrer"&gt;go-build&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Simple golang code builder&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://sparrowhub.io/plugin/go-format/0.000001" rel="noopener noreferrer"&gt;go-format&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Golang code formatter&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;They are simple yet useful, providing two essentials tools to build Golang code in terminal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Build code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Format code&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rest of the post shows how to use the plugins&lt;/p&gt;




&lt;h1&gt;
  
  
  Installation
&lt;/h1&gt;

&lt;p&gt;To install plugins we need to use &lt;a href="https://github.com/melezhik/Tomtit" rel="noopener noreferrer"&gt;Tomtit&lt;/a&gt; task runner with profiles - preset of Raku scenarios that grouped by topics:&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;#!bash&lt;/span&gt;

&lt;span class="nb"&gt;cd&lt;/span&gt; /to/you/source/code
tom &lt;span class="nt"&gt;--init&lt;/span&gt; &lt;span class="c"&gt;# initialize Tomtit for the first time&lt;/span&gt;
tom &lt;span class="nt"&gt;--profile&lt;/span&gt; go &lt;span class="c"&gt;# install go profile scenarios&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now once we've installed &lt;code&gt;go&lt;/code&gt; profile, we have an access to &lt;code&gt;go-build&lt;/code&gt;  and &lt;code&gt;go-format&lt;/code&gt; scenarios which are just a Raku wrappers to run mentioned plugins. Code is simple enough and editable to adjust your project specific needs&lt;/p&gt;

&lt;p&gt;Go-build scenario:&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;#!bash&lt;/span&gt;

tom &lt;span class="nt"&gt;--cat&lt;/span&gt; go-build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!raku&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cmd/app/main.go&lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt;
  &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cmd/cli/cli.go&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;


&lt;span class="nv"&gt;task&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;build&lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;go-build&lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$path&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;p&gt;Go-format scenario:&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;#!bash&lt;/span&gt;

tom &lt;span class="nt"&gt;--cat&lt;/span&gt; go-format
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!bash&lt;/span&gt;

task-run &lt;span class="s2"&gt;"go format"&lt;/span&gt;, &lt;span class="s2"&gt;"go-format"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Customization
&lt;/h1&gt;

&lt;p&gt;In case of &lt;code&gt;go-build&lt;/code&gt; scenario - adjust &lt;code&gt;path&lt;/code&gt; array with files to build for your project specific:&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;#!bash&lt;/span&gt;

&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;EDITOR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nano
tom &lt;span class="nt"&gt;--edit&lt;/span&gt; go-build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!raku&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cmd/app/app.go&lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt;
  &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cmd/app2/app2.go&lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;# the rest of the code stays the same &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Run
&lt;/h1&gt;

&lt;p&gt;To run plugins - use &lt;code&gt;tom&lt;/code&gt; cli runner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!bash
tom go-build
11:56:26 :: go build cmd/main.go
11:56:27 :: cmd/main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!bash
tom go-format
11:57:18 :: cmd/main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;That is it. &lt;/p&gt;

&lt;p&gt;Follow plugins &lt;a href="https://sparrowhub.io/search?q=%27go-%27" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; to get familiar with plugins parameters.&lt;/p&gt;

&lt;p&gt;Thanks for reading &lt;/p&gt;

</description>
      <category>rakulang</category>
      <category>raku</category>
      <category>go</category>
    </item>
    <item>
      <title>Simple search in source code with Tomtit and Sparrow</title>
      <dc:creator>Alexey Melezhik</dc:creator>
      <pubDate>Thu, 23 Jan 2025 20:21:22 +0000</pubDate>
      <link>https://forem.com/melezhik/simple-find-in-source-code-with-tomtit-and-sparrow-312</link>
      <guid>https://forem.com/melezhik/simple-find-in-source-code-with-tomtit-and-sparrow-312</guid>
      <description>&lt;p&gt;Recently, I've released a simple Sparrow plugin called &lt;a href="https://sparrowhub.io/plugin/find/0.000007" rel="noopener noreferrer"&gt;find&lt;/a&gt; to search text in source code, which is based on find and grep core linux utilities.&lt;/p&gt;

&lt;p&gt;The rest of the document shows how to perform search in terminal by using &lt;a href="https://github.com/melezhik/Tomtit" rel="noopener noreferrer"&gt;Tomtit&lt;/a&gt; task runner.&lt;/p&gt;




&lt;h1&gt;
  
  
  Installation
&lt;/h1&gt;

&lt;p&gt;To install the plugin, we need to use Tomtit task runner with profiles - preset of Raku scenarios that grouped by topics:&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;#!bash&lt;/span&gt;

&lt;span class="nb"&gt;cd&lt;/span&gt; /to/you/source/code
tom &lt;span class="nt"&gt;--init&lt;/span&gt; &lt;span class="c"&gt;# initialize Tomtit for the first time&lt;/span&gt;
tom &lt;span class="nt"&gt;--profile&lt;/span&gt; code &lt;span class="c"&gt;# install code profile scenarios&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now once we've installed &lt;code&gt;code&lt;/code&gt; profile scenarios (for now it's just a single one), we have an access to &lt;code&gt;search&lt;/code&gt; scenario which is just a Raku wrapper to do search with &lt;code&gt;find&lt;/code&gt; plugin:&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;#!bash&lt;/span&gt;

tom &lt;span class="nt"&gt;--cat&lt;/span&gt; search
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!raku&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$ext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;("&lt;/span&gt;&lt;span class="s2"&gt;ext (go): &lt;/span&gt;&lt;span class="p"&gt;");&lt;/span&gt;

&lt;span class="nv"&gt;$ext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;go&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="nv"&gt;$ext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$search1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;("&lt;/span&gt;&lt;span class="s2"&gt;search1: &lt;/span&gt;&lt;span class="p"&gt;");&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$search2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;("&lt;/span&gt;&lt;span class="s2"&gt;search2: &lt;/span&gt;&lt;span class="p"&gt;");&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$exclude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;("&lt;/span&gt;&lt;span class="s2"&gt;exclude: &lt;/span&gt;&lt;span class="p"&gt;");&lt;/span&gt;

&lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;find [&lt;/span&gt;&lt;span class="si"&gt;$search1&lt;/span&gt;&lt;span class="s2"&gt;] [&lt;/span&gt;&lt;span class="si"&gt;$search2&lt;/span&gt;&lt;span class="s2"&gt;] !&lt;/span&gt;&lt;span class="si"&gt;$exclude&lt;/span&gt;&lt;span class="s2"&gt; in &lt;/span&gt;&lt;span class="si"&gt;$ext&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;

&lt;span class="nv"&gt;task&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;find &lt;/span&gt;&lt;span class="si"&gt;$search1&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;$search2&lt;/span&gt;&lt;span class="s2"&gt; in &lt;/span&gt;&lt;span class="si"&gt;$ext&lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;find&lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$ext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$search1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;search2&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$search2&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;"",&lt;/span&gt;
  &lt;span class="s"&gt;exclude&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$exclude&lt;/span&gt; &lt;span class="o"&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;p&gt;The logic is following:&lt;/p&gt;

&lt;p&gt;‘search1’ filters results out by first string and then ‘search2’ filter is applied for final results, ‘ext’ defines files extension to search.&lt;/p&gt;

&lt;h1&gt;
  
  
  Customization
&lt;/h1&gt;

&lt;p&gt;Stub code shipped with Tomtit profile is good enough, we only need to change it a bit, to make default choice for file extenstion suitable for Raku project:&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;#!bash&lt;/span&gt;

&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;EDITOR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nano
tom &lt;span class="nt"&gt;--edit&lt;/span&gt; search
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!raku&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$ext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;("&lt;/span&gt;&lt;span class="s2"&gt;ext (rakumod): &lt;/span&gt;&lt;span class="p"&gt;");&lt;/span&gt;

&lt;span class="nv"&gt;$ext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rakumod&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="nv"&gt;$ext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;# ... the rest of the code is the same&lt;/span&gt;

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

&lt;/div&gt;



&lt;h1&gt;
  
  
  Search
&lt;/h1&gt;

&lt;p&gt;Now let's search files in my &lt;a href="https://github.com/melezhik/Tomtit" rel="noopener noreferrer"&gt;Tomtit&lt;/a&gt; repository. Say, I want to find all exported functions.&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;#!bash&lt;/span&gt;

tom search
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;load configuration from /home/melezhik/projects/Tomtit/.tom/env/config.raku
ext (rakumod): 
search1: sub
search2: is export
exclude: 
find [sub] [is export] ! in rakumod
23:09:06 :: find [sub] [is export] [!] in *.rakumod
23:09:06 :: ===
23:09:06 :: find . -name *.rakumod -exec grep --color -H 'sub' {} \; | grep 'is export'
23:09:06 :: case2
23:09:06 :: ./lib/Tomtit/Completion.rakumod:sub complete () is export {
23:09:06 :: ./lib/Tomtit.rakumod:our sub check-if-init ( $dir ) is export {
23:09:06 :: ./lib/Tomtit.rakumod:our sub init ($dir) is export {
23:09:06 :: ./lib/Tomtit.rakumod:our sub load-conf () is export {
23:09:06 :: ./lib/Tomtit.rakumod:sub tomtit-usage () is export  {
23:09:06 :: ./lib/Tomtit.rakumod:sub tomtit-help () is export  {
23:09:06 :: ./lib/Tomtit.rakumod:sub tomtit-clean ($dir) is export { 
23:09:06 :: ./lib/Tomtit.rakumod:sub scenario-last ($dir) is export {
23:09:06 :: ./lib/Tomtit.rakumod:sub scenario-run ($dir,$scenario,%args?) is export {
23:09:06 :: ./lib/Tomtit.rakumod:sub scenario-remove ($dir,$scenario) is export {
23:09:06 :: ./lib/Tomtit.rakumod:sub scenario-cat ($dir,$scenario,%args?) is export {
23:09:06 :: ./lib/Tomtit.rakumod:sub scenario-edit ($dir,$scenario) is export {
23:09:06 :: ./lib/Tomtit.rakumod:sub environment-edit ($dir,$env) is export {
23:09:06 :: ./lib/Tomtit.rakumod:sub environment-list ($dir) is export {
23:09:06 :: ./lib/Tomtit.rakumod:sub environment-set ($dir,$env) is export {
23:09:06 :: ./lib/Tomtit.rakumod:sub environment-show ($dir) is export {
23:09:06 :: ./lib/Tomtit.rakumod:sub environment-cat ($dir,$env,%args?) is export {
23:09:06 :: ./lib/Tomtit.rakumod:sub scenario-doc ($dir,$scenario) is export {
23:09:06 :: ./lib/Tomtit.rakumod:sub scenario-list ($dir) is export {
23:09:06 :: ./lib/Tomtit.rakumod:sub scenario-list-print ($dir) is export {
23:09:06 :: ./lib/Tomtit.rakumod:multi sub profile-list  is export {
23:09:06 :: ./lib/Tomtit.rakumod:multi sub profile-list($dir,$profile is copy)  is export {
23:09:06 :: ./lib/Tomtit.rakumod:sub profile-install ($dir, $profile is copy, %args?) is export {
23:09:06 :: ./lib/Tomtit.rakumod:sub completion-install () is export {
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;That is it. Follow plugin &lt;a href="https://sparrowhub.io/plugin/find/0.000007" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; to get familiar with plugin parameters.&lt;/p&gt;

&lt;p&gt;Thanks for reading&lt;/p&gt;

</description>
      <category>search</category>
      <category>raku</category>
      <category>rakulang</category>
      <category>sparrow</category>
    </item>
    <item>
      <title>Developing a simple Sparrow plugin with Raku and Bash</title>
      <dc:creator>Alexey Melezhik</dc:creator>
      <pubDate>Mon, 20 Jan 2025 14:27:08 +0000</pubDate>
      <link>https://forem.com/melezhik/developing-a-simple-sparrow-plugin-with-raku-and-bash-51g0</link>
      <guid>https://forem.com/melezhik/developing-a-simple-sparrow-plugin-with-raku-and-bash-51g0</guid>
      <description>&lt;p&gt;Sparrow is very efficient in writing scripts to automate system management using Raku and Bash, in today's post I am going to show an example ...&lt;/p&gt;




&lt;h2&gt;
  
  
  Deploy configuration file and restart service upon changes
&lt;/h2&gt;

&lt;p&gt;Let's create a simple plugin that enable / disable some configuration flags in service configuration file and if any changes occur reload a service. Format of flags in configuration file is following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var=true|false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is an example of configuration file:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;config.txt&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;debug=true
sentry=false
tls=false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usually services are managed as systemd units, so &lt;code&gt;service reload&lt;/code&gt; could be used to reload service configuration.&lt;/p&gt;

&lt;p&gt;——&lt;/p&gt;

&lt;p&gt;Let’s get started and create root folder for the plugin:&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;#!bash&lt;/span&gt;

&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; service-config-reload
&lt;span class="nb"&gt;cd &lt;/span&gt;service-config-reload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now inside plugin root directory:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;hook.raku&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!raku&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$cfg&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;orig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;config&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="nv"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;slurp&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$cfg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$cfg&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;orig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;config&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;enable&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$f&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nv"&gt;$cfg&lt;/span&gt; &lt;span class="o"&gt;~~&lt;/span&gt; &lt;span class="sr"&gt;s/&amp;lt;?wb&amp;gt; "$f=" \S+ /$f=true/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;config&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;disable&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$f&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nv"&gt;$cfg&lt;/span&gt; &lt;span class="o"&gt;~~&lt;/span&gt; &lt;span class="sr"&gt;s/&amp;lt;?wb&amp;gt; "$f=" \S+ /$f=false/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;set_stdout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cfg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;$cfg&lt;/span&gt; &lt;span class="ow"&gt;ne&lt;/span&gt; &lt;span class="nv"&gt;$cfg&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;orig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nv"&gt;set_stdout&lt;/span&gt;&lt;span class="p"&gt;("&lt;/span&gt;&lt;span class="s2"&gt;config changed&lt;/span&gt;&lt;span class="p"&gt;");&lt;/span&gt;
   &lt;span class="nv"&gt;config&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="nv"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;spurt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cfg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="nv"&gt;set_stdout&lt;/span&gt;&lt;span class="p"&gt;("&lt;/span&gt;&lt;span class="s2"&gt;config updated&lt;/span&gt;&lt;span class="p"&gt;");&lt;/span&gt;
   &lt;span class="nv"&gt;run_task&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;service_restart&lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
       &lt;span class="s"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;config&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;service&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="s"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;config&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="o"&gt;&amp;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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir -p tasks/service_restart/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;tasks/service_restart/task.bash&lt;/code&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;#!bash&lt;/span&gt;

&lt;span class="nb"&gt;sudo &lt;/span&gt;service &lt;span class="nv"&gt;$service&lt;/span&gt; reload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In real life applications services might have a linter capability where before configuration is applied it's checked and if any error occur application of changes is terminated:&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;#!bash&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;
&lt;span class="nb"&gt;sudo&lt;/span&gt; /usr/bin/&lt;span class="nv"&gt;$service&lt;/span&gt; &lt;span class="nt"&gt;--check&lt;/span&gt; &lt;span class="nv"&gt;$path&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;service &lt;span class="nv"&gt;$service&lt;/span&gt; reload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Publish plugin
&lt;/h1&gt;

&lt;p&gt;Packaging scenario as a plugin will allow users to reuse it as a library.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sparrow.json&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"service-config-reload"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Deploy configuration file and restart service upon changes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"linux"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/melezhik/sparrow-plugins/tree/master/service-config-reload"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This last command will upload plugin to local Sparrow repository (see this &lt;a href="https://github.com/melezhik/Sparrow6/blob/master/documentation/repository.md#create-repository" rel="noopener noreferrer"&gt;doc&lt;/a&gt; for more details)&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;#!bash&lt;/span&gt;

s6 &lt;span class="nt"&gt;--upload&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Use of plugins
&lt;/h2&gt;

&lt;p&gt;Anywhere in Raku scenario, just do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!raku&lt;/span&gt;

&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Sparrow6::&lt;/span&gt;&lt;span class="nv"&gt;DSL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;task&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;apply and reload&lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;service-config-reload&lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;txt&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;service&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;tls&lt;/span&gt; &lt;span class="nv"&gt;sentry&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# enable TLS and use of Sentry for production&lt;/span&gt;
   &lt;span class="s"&gt;disable&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# disable debug mode for production &lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Thanks for reading!&lt;/p&gt;

&lt;h1&gt;
  
  
  PS
&lt;/h1&gt;

&lt;p&gt;Source code of the plugin could be found here - &lt;a href="https://github.com/melezhik/sparrow-plugins/tree/master/service-config-reload" rel="noopener noreferrer"&gt;https://github.com/melezhik/sparrow-plugins/tree/master/service-config-reload&lt;/a&gt;&lt;/p&gt;

</description>
      <category>sparrow</category>
      <category>raku</category>
      <category>bash</category>
      <category>linux</category>
    </item>
    <item>
      <title>Go pipelines with Raku interfaces</title>
      <dc:creator>Alexey Melezhik</dc:creator>
      <pubDate>Tue, 05 Nov 2024 20:47:34 +0000</pubDate>
      <link>https://forem.com/melezhik/go-pipelines-with-raku-interfaces-j7o</link>
      <guid>https://forem.com/melezhik/go-pipelines-with-raku-interfaces-j7o</guid>
      <description>&lt;p&gt;So, let's say you're golang developer and want pure Go to write some CICD task:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cat task.go&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"fmt"&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="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;"Hello, pipeline"&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;p&gt;Go is cool, but there is one thing that makes it difficult to use in high level scenarios - its verbosity. Passing parameters to go tasks and return them back to main scenario takes some efforts and a lot of boilerplate code. It'd be good to keep main code concise and easy to read. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://raku.org" rel="noopener noreferrer"&gt;Rakulang&lt;/a&gt; in other hand is perfect language when it comes to munge data in and out, due it's extreme flexibility and expressiveness. &lt;/p&gt;

&lt;p&gt;In this post I am going to show how to embed golang tasks into CICD pipelines, with a little help of Sparrow framework.&lt;/p&gt;




&lt;p&gt;Let's, first of all, modify our golang task code, new version will be:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cat task.go&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;"fmt"&lt;/span&gt;
  &lt;span class="s"&gt;"github.com/melezhik/sparrowgo"&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;type&lt;/span&gt; &lt;span class="n"&gt;Params&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;Message&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;type&lt;/span&gt; &lt;span class="n"&gt;Result&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;Message&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;var&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="n"&gt;Params&lt;/span&gt;

  &lt;span class="c"&gt;// sparrowgo takes care about passing &lt;/span&gt;
  &lt;span class="c"&gt;// input parameters from Raku to Go&lt;/span&gt;
  &lt;span class="n"&gt;sparrowgo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c"&gt;// read params from pipeline&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;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Task params: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c"&gt;// return results back to pipeline &lt;/span&gt;
  &lt;span class="n"&gt;sparrowgo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UpdateState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Hello from Go"&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;p&gt;All we've done here is utilized Sparrowgo package that "convert" golang task into Sparrow task with a benefits of passing and returning data from and to Rakulang.  &lt;/p&gt;




&lt;p&gt;Finally this how our pipeline will look like, now it's Raku part:&lt;br&gt;
&lt;/p&gt;

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

my $s = task-run ".", %(
  :message&amp;lt;Hello from Raku&amp;gt;
);

say "Result: ", $s&amp;lt;Message&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;High level design.&lt;/p&gt;

&lt;p&gt;Now, once we have some prove of concept code in place, we can get a high level picture of what our pipeline system could look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      [ Raku scenario to pass and handle data in and out ]
        \                    \                     \          
      task.go -&amp;gt; result -&amp;gt; task.go -&amp;gt; result -&amp;gt; task.go -&amp;gt; ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, we have the best of two worlds - Raku to write scenarios with less of code and Golang to do all heaving lifting where performance and strict type checking is required. &lt;/p&gt;

</description>
      <category>rakulang</category>
      <category>go</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Sparky - composable user interfaces for internal services</title>
      <dc:creator>Alexey Melezhik</dc:creator>
      <pubDate>Mon, 01 Jul 2024 15:09:30 +0000</pubDate>
      <link>https://forem.com/melezhik/sparky-composable-user-interfaces-for-internal-services-3i7k</link>
      <guid>https://forem.com/melezhik/sparky-composable-user-interfaces-for-internal-services-3i7k</guid>
      <description>&lt;p&gt;Sparky recent releases have introduced a lot of features allowing to build user interfaces for internal web applications.&lt;/p&gt;




&lt;p&gt;In a nutshell &lt;a href="https://github.com/melezhik/sparky" rel="noopener noreferrer"&gt;Sparky&lt;/a&gt; is a web platform to run automation tasks, and Sparky equips you with a rich feature set to build a frontend to launch tasks with parameters.&lt;/p&gt;

&lt;h1&gt;
  
  
  HTML controls
&lt;/h1&gt;

&lt;p&gt;Let's brew some coffee, even though I've been trying to slow down on caffeine recently.&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;allow_manual_run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;vars&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;Flavor&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;latte"&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;select&lt;/span&gt; 
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;espresso&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;amerikano&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;latte&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;Topic&lt;/span&gt;  
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;milk"&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;select&lt;/span&gt;
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;milk&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;cream&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;cinnamon&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;multiple&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;Step3&lt;/span&gt; 
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;boiled&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;water"&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;input&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This simple Sparky job definition will spin up a simple UI with 3 controls:&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%2Fful881r6w8tpa6k8hh39.jpeg" 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%2Fful881r6w8tpa6k8hh39.jpeg" alt="UI" width="656" height="1176"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In Raku scenario those input parameters are handled like that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$flavor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;Flavor&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$water&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;Step3&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;@topics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;# tags() needs :array modifier&lt;/span&gt;
&lt;span class="c1"&gt;# as multiple choices are passed&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;(:&lt;/span&gt;&lt;span class="nv"&gt;array&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;Topic&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$t&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nv"&gt;@topics&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$t&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;p&gt;Simple, huh? &lt;/p&gt;

&lt;p&gt;One more coffee, please! &lt;/p&gt;

&lt;h1&gt;
  
  
  Or maybe you like a tea?
&lt;/h1&gt;

&lt;p&gt;Another cool feature of Sparky is a multi scenario flows or group variables. Let's say you would like to give a choice of coffer &lt;em&gt;or&lt;/em&gt; tea, so instead of having 2 job definitions with different sets of variables, let's create just one:&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;vars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

  &lt;span class="c1"&gt;# tea vars&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;Flavor&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;black"&lt;/span&gt; 
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;select&lt;/span&gt; 
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;black&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;green&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;tea&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;Topic&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;milk"&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;select&lt;/span&gt;
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;milk&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;cream&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;tea&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="c1"&gt;# coffee vars&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;Flavor&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;latte"&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;select&lt;/span&gt; 
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;espresso&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;amerikano&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;latte&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;coffee&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;Topic&lt;/span&gt;  
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;milk"&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;select&lt;/span&gt;
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;milk&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;cream&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;cinnamon&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;coffee&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;multiple&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="c1"&gt;# common vars&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;Step3&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;boiled&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;water"&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;input&lt;/span&gt;
    &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;tea&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;coffee&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# every var&lt;/span&gt;
&lt;span class="c1"&gt;# in group vars&lt;/span&gt;
&lt;span class="c1"&gt;# is a separate&lt;/span&gt;
&lt;span class="c1"&gt;# scenario &lt;/span&gt;

&lt;span class="na"&gt;group_vars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;tea&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;coffee&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when the job gets run, we have a &lt;em&gt;choice&lt;/em&gt;:&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%2Fgithub.com%2Fmelezhik%2Fimages%2Fblob%2Fmaster%2FImage%252001.07.2024%2520at%252017.40.jpeg%3Fraw%3Dtrue" 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%2Fgithub.com%2Fmelezhik%2Fimages%2Fblob%2Fmaster%2FImage%252001.07.2024%2520at%252017.40.jpeg%3Fraw%3Dtrue" alt="UI2" width="658" height="680"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And then (when click on proper link), let's enjoy some tea:&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%2Fgithub.com%2Fmelezhik%2Fimages%2Fblob%2Fmaster%2FImage%252001.07.2024%2520at%252017.42.jpeg%3Fraw%3Dtrue" 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%2Fgithub.com%2Fmelezhik%2Fimages%2Fblob%2Fmaster%2FImage%252001.07.2024%2520at%252017.42.jpeg%3Fraw%3Dtrue" alt="UI3" width="852" height="962"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Sparky is a nice web console to build internal automation services with HTML and write automation scenarios with Raku. Thanks for reading.&lt;/p&gt;

</description>
      <category>ui</category>
      <category>html</category>
      <category>raku</category>
      <category>automation</category>
    </item>
    <item>
      <title>Sparky - hacking minikube with mini tool</title>
      <dc:creator>Alexey Melezhik</dc:creator>
      <pubDate>Fri, 14 Jun 2024 13:56:10 +0000</pubDate>
      <link>https://forem.com/melezhik/sparky-hacking-minikube-with-mini-tool-3jl8</link>
      <guid>https://forem.com/melezhik/sparky-hacking-minikube-with-mini-tool-3jl8</guid>
      <description>&lt;p&gt;TL; DR: How to deploy docker to minikube when one does not need anything fancy, but pure Raku.&lt;/p&gt;




&lt;p&gt;So, you have your own pet K8s cluster deployed as minikube and you want to play with it. You have few microservices to build and you don't want to bother with kubernetes low level commands at all.&lt;/p&gt;

&lt;p&gt;On the other hand, you setup is &lt;em&gt;complex enough&lt;/em&gt; to express it in a bunch of yaml files or kubectl commands. Here is an elegant way to handle this in pure Raku, and it's called Sparky ...&lt;/p&gt;




&lt;h1&gt;
  
  
  Show me the design
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      |-------------------------------------|
      | Sparky -&amp;gt; kubectl -&amp;gt; MiniKube       | 
      |                     /\  /\  /\      |
      |                     pod pod pod     |
      |-------------------------------------| 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the infrastructure part is simple - on the same host we install minikube and Sparky that underlying uses kubectl to deploy containers into k8s cluster.&lt;/p&gt;

&lt;h1&gt;
  
  
  Show me the code
&lt;/h1&gt;

&lt;p&gt;As usually Sparky job is a pure Raku code, but this time some plugins will be of use as well ...&lt;/p&gt;

&lt;p&gt;Sparky is integrated with - Sparrowhub - &lt;a href="https://sparrowhub.io" rel="noopener noreferrer"&gt;https://sparrowhub.io&lt;/a&gt; - repository of Sparrow plugins - useful building blocks for any sort of automation.&lt;/p&gt;

&lt;p&gt;Let's use a couple of them - k8s-deployment and k8s-pod-check to deploy and check Kubernetes pods. From Sparky point of view those are &lt;em&gt;just Raku functions&lt;/em&gt;, with some input parameters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="nv"&gt;task&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dpl create&lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;k8s-deployment&lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;deployment_name&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;nginx&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;app_name&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;nginx&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;nginx:1&lt;/span&gt;&lt;span class="mf"&gt;.14.2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="nv"&gt;replicas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;


&lt;span class="c1"&gt;# give it some time to allow all pods to start ...&lt;/span&gt;
&lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;task&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;nginx pod check&lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;k8s-pod-check&lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;nginx&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;namespace&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;default&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;die&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;on&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;check&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="nv"&gt;num&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;p&gt;For the tutorial purpose we are going to deploy nginx server with 3 replicas, by using &lt;a href="https://sparrowhub.io/plugin/k8s-deployment/0.000004" rel="noopener noreferrer"&gt;k8s-deployment&lt;/a&gt; plugin.&lt;/p&gt;

&lt;p&gt;Let's give it a try.&lt;/p&gt;

&lt;h1&gt;
  
  
  First run
&lt;/h1&gt;

&lt;p&gt;And the very first deploy ... fails:&lt;br&gt;
&lt;/p&gt;

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

11:03:34 :: deployment.apps/nginx created
11:03:34 :: [repository] - installing k8s-pod-check, version 0.000012
11:03:34 :: [repository] - install Data::Dump to /home/astra/.sparrowdo/minikube/sparrow6/plugins/k8s-pod-check/raku-lib
All candidates are currently installed
No reason to proceed. Use --force-install to continue anyway
[task run: task.pl6 - nginx pod check]
[task stdout]
11:03:41 :: ${:die-on-check-fail(Bool::True), :name("nginx"), :namespace("default"), :num(3)}
11:03:41 :: ===========================
11:03:41 :: NAME                           READY   STATUS             RESTARTS         AGE
11:03:41 :: nginx-77d8468669-5gxbf         0/1     ErrImagePull       0                5s
11:03:41 :: nginx-77d8468669-c5vbl         0/1     ErrImagePull       0                5s
11:03:41 :: nginx-77d8468669-lhc54         0/1     ErrImagePull       0                5s
11:03:41 :: ===========================
11:03:41 :: nginx-77d8468669-5gxbf POD_NOT_OK
11:03:41 :: nginx-77d8468669-c5vbl POD_NOT_OK
11:03:41 :: nginx-77d8468669-lhc54 POD_NOT_OK
[task check]
stdout match &amp;lt;^^ 'nginx' \S+ \s+ POD_OK $$&amp;gt; False
---
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although Kubernetes deployment has been successfully created, further &lt;a href="https://sparrowhub.io/plugin/k8s-pod-check/0.000012" rel="noopener noreferrer"&gt;k8s-pod-check&lt;/a&gt; failed to verify that all pods are running. &lt;/p&gt;

&lt;p&gt;Use of &lt;code&gt;die-on-check-fail&lt;/code&gt; option made the job stops strait away after this point.&lt;/p&gt;

&lt;p&gt;The reason is in &lt;code&gt;ErrImagePull&lt;/code&gt; - nginx docker image is not accessible from within a minikube, which a known minkube DNS issue, which is easy to fix.&lt;/p&gt;

&lt;h1&gt;
  
  
  It's fixed!
&lt;/h1&gt;

&lt;p&gt;All we need to do is to upload nginx docker image  &lt;em&gt;manually&lt;/em&gt;, so that minukube will pick it up from a file cache:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;minikube image load nginx:1.14.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when have restarted the failed job we get this:&lt;br&gt;
&lt;/p&gt;

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

11:05:42 :: deployment.apps/nginx unchanged
[task run: task.pl6 - nginx pod check]
[task stdout]
11:05:47 :: ${:die-on-check-fail(Bool::False), :name("nginx"), :namespace("default"), :num(3)}
11:05:47 :: ===========================
11:05:47 :: NAME                           READY   STATUS             RESTARTS         AGE
11:05:47 :: nginx-77d8468669-5gxbf         1/1     Running            0                2m10s
11:05:47 :: nginx-77d8468669-c5vbl         1/1     Running            0                2m10s
11:05:47 :: nginx-77d8468669-lhc54         1/1     Running            0                2m10s
11:05:47 :: ===========================
11:05:47 :: nginx-77d8468669-5gxbf POD_OK
11:05:47 :: nginx-77d8468669-c5vbl POD_OK
11:05:47 :: nginx-77d8468669-lhc54 POD_OK
[task check]
stdout match &amp;lt;^^ 'nginx' \S+ \s+ POD_OK $$&amp;gt; True
&amp;lt;3 pods are running&amp;gt; True
---
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last deployment has not changed (with is denoted by "deployment.apps/nginx unchanged" line), as we did not change anything, however minikube now is able to pick the recently uploaded docker image and all pods now are running.&lt;/p&gt;

&lt;p&gt;Congratulation with the very first successfully deployment to Kubernetes via Sparky!&lt;/p&gt;

&lt;h1&gt;
  
  
  Clean up
&lt;/h1&gt;

&lt;p&gt;In the end let's remove our test pods, by using k8s-deployment plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="nv"&gt;task&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dpl delete&lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;k8s-deployment&lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;deployment_name&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;nginx&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;action&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;delete&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Further thoughts
&lt;/h1&gt;

&lt;p&gt;This simple scenario is going to give us some ideas on how to deploy to Kubernetes in imperative way using pure Raku, I, personally like this approach better, as having a bunch of helm charts and yaml files seems overkill when one need just to deploy some none production code, however, as always YMMV, thanks for reading ...&lt;/p&gt;

</description>
      <category>k8s</category>
      <category>raku</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Sparky - simple and efficient alternative to Ansible</title>
      <dc:creator>Alexey Melezhik</dc:creator>
      <pubDate>Fri, 31 May 2024 13:22:49 +0000</pubDate>
      <link>https://forem.com/melezhik/sparky-simple-and-efficient-alternative-to-ansible-1fod</link>
      <guid>https://forem.com/melezhik/sparky-simple-and-efficient-alternative-to-ansible-1fod</guid>
      <description>&lt;h2&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%2Fqux8rwm2igz2ui5rt48e.png" alt="SparkyLogo" width="120" height="120"&gt;
&lt;/h2&gt;

&lt;p&gt;So, you have a hundred VMs you need to manage, and you have ... Ansible ? I should stop here, as this is a tool that is standard in configuration management nowadays, but I'd dare to continue and say there is a better alternative to it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;But before we get into it, why am I so frustrated with Ansible? Here are my points:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;YAML based declarative DSL really stinks on complex tasks as it lacks the flexibility that imperative languages have.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;YAML is not even a programming language, and you gonna pay the price very soon.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To keep ansible code clean and simple, extra efforts are required, one need to refactor out all the complexity from YAML to python modules and this feels like "why I &lt;em&gt;even&lt;/em&gt; start using YAML DSL"?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ansible reports are frustrating as I always need to add these debug tasks to show real STDOUT/STDERR emitted from commands, where it should just work out of the box.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ansible ties me to the idea of "running on a host," where sometimes I need to run tasks not tied to hosts, yes, you can still use "ansible_connection=local" but this feels awkward.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Meet Sparky
&lt;/h1&gt;

&lt;p&gt;So, meet Sparky - elegant, efficient and all-battery included automation tool. It's written on powerful and modern &lt;a href="http://raku.org" rel="noopener noreferrer"&gt;Raku&lt;/a&gt; language, with &lt;a href="https://bulma.io" rel="noopener noreferrer"&gt;bulma&lt;/a&gt; css frontend and web sockets.&lt;/p&gt;

&lt;p&gt;To install Sparky - install &lt;a href="https://rakudo.org" rel="noopener noreferrer"&gt;Rakudo&lt;/a&gt; first and then install Sparky itself as a Raku module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl https://rakubrew.org/install-on-perl.sh | sh 
eval "$(~/.rakubrew/bin/rakubrew init Bash)" 
rakubrew download moar-2024.05
git clone https://github.com/melezhik/sparky.git
cd sparky/
# install Sparky and it's dependencies
zef install --/test .
# init sparky sqlite database
raku db-init.raku
# run sparky job runner
nohup sparkyd &amp;gt;~/.sparkyd.log &amp;lt; /dev/null &amp;amp;
# run sparky web console
cro run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This simple scenario gets it up and running; if you go to &lt;a href="http://127.0.0.1:4000" rel="noopener noreferrer"&gt;http://127.0.0.1:4000&lt;/a&gt; you'll see a nice Sparky web console. We use the console to run sparky jobs.&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%2Fxrecpkwigt4o346zqvj8.jpeg" 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%2Fxrecpkwigt4o346zqvj8.jpeg" alt="SparkyUI" width="800" height="744"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Show me the design
&lt;/h1&gt;

&lt;p&gt;So we have a control plane that would manage many hosts over ssh, using push mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;         ---------------
         | CP , Sparky | 
         ---------------
              [ssh] 
       /   /    |    \    \
     host host host host host

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

&lt;/div&gt;



&lt;p&gt;This is pretty much what ansible does ...&lt;/p&gt;

&lt;h1&gt;
  
  
  Show me the code
&lt;/h1&gt;

&lt;p&gt;Now say, we have 5 NGINX servers we need to restart, let's drop a simple Sparky job to do this in pure &lt;a href="https://raku.org" rel="noopener noreferrer"&gt;Raku&lt;/a&gt; language:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Sparky::&lt;/span&gt;&lt;span class="nv"&gt;JobApi&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;class&lt;/span&gt; &lt;span class="nv"&gt;Pipeline&lt;/span&gt; &lt;span class="nv"&gt;does&lt;/span&gt; &lt;span class="nn"&gt;Sparky::JobApi::&lt;/span&gt;&lt;span class="nv"&gt;Role&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="nv"&gt;method&lt;/span&gt; &lt;span class="nv"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;5&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="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;job&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;workers&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
      &lt;span class="nv"&gt;$j&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;queue:&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;sparrowdo&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="s"&gt;bootstrap&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;nginx_{&lt;/span&gt;&lt;span class="si"&gt;$i&lt;/span&gt;&lt;span class="s2"&gt;}.local.domain&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
       &lt;span class="s"&gt;tags&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
         &lt;span class="s"&gt;stage&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;child&lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt;
         &lt;span class="s"&gt;i&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="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="nv"&gt;method&lt;/span&gt; &lt;span class="nv"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;child&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;service&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;restart&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;nginx&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;p&gt;In this scenario, Sparky will run five parallel jobs that restart nginx on five hosts. Simple and elegant.&lt;/p&gt;

&lt;p&gt;Moreover those five jobs will appear as five separate reports in Sparky UI …&lt;/p&gt;

&lt;h1&gt;
  
  
  Got interested?
&lt;/h1&gt;

&lt;p&gt;Of course, this is only a quick glance at Sparky architecture and features, things to cover further:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Job orchestration (DAGs)&lt;/li&gt;
&lt;li&gt;Core DSL (pure Raku)&lt;/li&gt;
&lt;li&gt;Custom UIs&lt;/li&gt;
&lt;li&gt;Authentication (oauth2) and security access list&lt;/li&gt;
&lt;li&gt;Writing more sophisticated scenarios&lt;/li&gt;
&lt;li&gt;Extending Sparky with plugins by using many programming languages&lt;/li&gt;
&lt;li&gt;Installing Sparky with MySQL/Postgresql database storage (instead of sqlite) &lt;/li&gt;
&lt;li&gt;Using Sparky as CI server (SCM triggering and cron jobs)&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Links
&lt;/h1&gt;

&lt;p&gt;Sparky project - &lt;a href="https://github.com/melezhik/sparky" rel="noopener noreferrer"&gt;https://github.com/melezhik/sparky&lt;/a&gt;&lt;/p&gt;

</description>
      <category>raku</category>
      <category>automation</category>
      <category>ansible</category>
    </item>
    <item>
      <title>Sparky - Self-hosted Task Runner with Easily Customizable UI</title>
      <dc:creator>Alexey Melezhik</dc:creator>
      <pubDate>Tue, 02 Jan 2024 14:25:42 +0000</pubDate>
      <link>https://forem.com/melezhik/sparky-self-hosted-task-runner-with-easily-customizable-ui-4m0n</link>
      <guid>https://forem.com/melezhik/sparky-self-hosted-task-runner-with-easily-customizable-ui-4m0n</guid>
      <description>&lt;p&gt;Sparky is a task-runner allow teams to automate their daily tasks by creating #Rakulang scenarios and customizable UI&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="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.sparky/projects/build-rakudo/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;nano ~/.sparky/projects/build-rakudo/sparky.yaml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;sparrowdo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;no_sudo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;no_index_update&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;bootstrap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;allow_manual_run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;vars&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;version&lt;/span&gt;
      &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2023.12"&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;input&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;arch&lt;/span&gt;
      &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;alpine&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;debian&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ubuntu&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;select&lt;/span&gt;
      &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;alpine&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;nano ~/.sparky/projects/build-rakudo/sparrowfile&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!raku&lt;/span&gt;
&lt;span class="nv"&gt;task&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;files/build-rakudo&lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;rakudo_version&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;arch&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;arch&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this imaginary scenario we want to build a Rakudo docker image for a specific Rakudo version and Linux distribution:&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="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.sparky/projects/build-rakudo/files/build/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;nano ~/.sparky/projects/build-rakudo/files/build/task.bash&lt;/code&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="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;HERE&lt;/span&gt;&lt;span class="sh"&gt; &amp;gt; &lt;/span&gt;&lt;span class="nv"&gt;$cache_root_dir&lt;/span&gt;&lt;span class="sh"&gt;/install-rakudo.sh
mkdir ~/rakudo &amp;amp;&amp;amp; cd &lt;/span&gt;&lt;span class="nv"&gt;$_&lt;/span&gt;&lt;span class="sh"&gt;
curl -LJO https://rakudo.org/dl/rakudo/rakudo-&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="sh"&gt;.tar.gz
tar -xvzf rakudo-*.tar.gz
cd rakudo-*
perl Configure.pl --backend=moar --gen-moar
make
make install
&lt;/span&gt;&lt;span class="no"&gt;HERE

&lt;/span&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;config &lt;span class="nb"&gt;arch&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"alpine"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;HERE&lt;/span&gt;&lt;span class="sh"&gt; &amp;gt; &lt;/span&gt;&lt;span class="nv"&gt;$cache_root_dir&lt;/span&gt;&lt;span class="sh"&gt;/Dockerfile
FROM alpine:latest
ARG rakudo_version=2023.12
RUN apk update &amp;amp;&amp;amp; apk add git make gcc musl-dev
RUN adduser -D -h /home/worker -s /bin/bash -G wheel worker
USER worker
ENV PATH="/home/worker/rakudo-&lt;/span&gt;&lt;span class="nv"&gt;$rakudo_version&lt;/span&gt;&lt;span class="sh"&gt;/install/bin:/home/worker/rakudo-&lt;/span&gt;&lt;span class="nv"&gt;$rakudo_version&lt;/span&gt;&lt;span class="sh"&gt;/install/share/perl6/vendor/bin:/home/worker/rakudo-&lt;/span&gt;&lt;span class="nv"&gt;$rakudo_version&lt;/span&gt;&lt;span class="sh"&gt;/install/share/perl6/core/bin:/home/worker/rakudo-&lt;/span&gt;&lt;span class="nv"&gt;$rakudo_version&lt;/span&gt;&lt;span class="sh"&gt;/install/share/perl6/site/bin:/home/worker/.raku/bin:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"
COPY install-rakudo.sh . 
RUN sh ./install-rakudo.sh &lt;/span&gt;&lt;span class="nv"&gt;$rakudo_version&lt;/span&gt;&lt;span class="sh"&gt;
&lt;/span&gt;&lt;span class="no"&gt;HERE
&lt;/span&gt;&lt;span class="k"&gt;elif &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;config &lt;span class="nb"&gt;arch&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"debian"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;HERE&lt;/span&gt;&lt;span class="sh"&gt; &amp;gt; &lt;/span&gt;&lt;span class="nv"&gt;$cache_root_dir&lt;/span&gt;&lt;span class="sh"&gt;/Dockerfile
FROM debian:latest
ARG rakudo_version=2023.12
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update -q -o Dpkg::Use-Pty=0
RUN apt-get install -q -y -o Dpkg::Use-Pty=0 build-essential curl git
RUN useradd -m  -d /home/worker --shell /bin/bash worker
USER worker
ENV PATH="/home/worker/rakudo-&lt;/span&gt;&lt;span class="nv"&gt;$rakudo_version&lt;/span&gt;&lt;span class="sh"&gt;/install/bin:/home/worker/rakudo-&lt;/span&gt;&lt;span class="nv"&gt;$rakudo_version&lt;/span&gt;&lt;span class="sh"&gt;/install/share/perl6/vendor/bin:/home/worker/rakudo-&lt;/span&gt;&lt;span class="nv"&gt;$rakudo_version&lt;/span&gt;&lt;span class="sh"&gt;/install/share/perl6/core/bin:/home/worker/rakudo-&lt;/span&gt;&lt;span class="nv"&gt;$rakudo_version&lt;/span&gt;&lt;span class="sh"&gt;/install/share/perl6/site/bin:/home/worker/.raku/bin:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"
COPY install-rakudo.sh . 
RUN sh ./install-rakudo.sh &lt;/span&gt;&lt;span class="nv"&gt;$rakudo_version&lt;/span&gt;&lt;span class="sh"&gt;
&lt;/span&gt;&lt;span class="no"&gt;HERE
&lt;/span&gt;&lt;span class="k"&gt;elif &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;config &lt;span class="nb"&gt;arch&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"ubuntu"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;HERE&lt;/span&gt;&lt;span class="sh"&gt; &amp;gt; &lt;/span&gt;&lt;span class="nv"&gt;$cache_root_dir&lt;/span&gt;&lt;span class="sh"&gt;/Dockerfile
FROM ubuntu:latest
ARG rakudo_version=2023.12
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update -q -o Dpkg::Use-Pty=0
RUN apt-get install -q -y -o Dpkg::Use-Pty=0 build-essential curl git
RUN useradd -m  -d /home/worker --shell /bin/bash worker
USER worker
ENV PATH="/home/worker/rakudo-&lt;/span&gt;&lt;span class="nv"&gt;$rakudo_version&lt;/span&gt;&lt;span class="sh"&gt;/install/bin:/home/worker/rakudo-&lt;/span&gt;&lt;span class="nv"&gt;$rakudo_version&lt;/span&gt;&lt;span class="sh"&gt;/install/share/perl6/vendor/bin:/home/worker/rakudo-&lt;/span&gt;&lt;span class="nv"&gt;$rakudo_version&lt;/span&gt;&lt;span class="sh"&gt;/install/share/perl6/core/bin:/home/worker/rakudo-&lt;/span&gt;&lt;span class="nv"&gt;$rakudo_version&lt;/span&gt;&lt;span class="sh"&gt;/install/share/perl6/site/bin:/home/worker/.raku/bin:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"
COPY install-rakudo.sh . 
RUN sh ./install-rakudo.sh &lt;/span&gt;&lt;span class="nv"&gt;$rakudo_version&lt;/span&gt;&lt;span class="sh"&gt;
&lt;/span&gt;&lt;span class="no"&gt;HERE
&lt;/span&gt;&lt;span class="k"&gt;else
&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;config &lt;span class="nb"&gt;arch&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; is not supported"&lt;/span&gt;
&lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;docker build &lt;span class="nv"&gt;$cache_root_dir&lt;/span&gt;/ &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;$cache_root_dir&lt;/span&gt;/Dockerfile &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--build-arg&lt;/span&gt; &lt;span class="nv"&gt;rakudo_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;config rakudo_version&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-t&lt;/span&gt; team/rakudo:&lt;span class="si"&gt;$(&lt;/span&gt;config &lt;span class="nb"&gt;arch&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;-&lt;span class="si"&gt;$(&lt;/span&gt;config version&lt;span class="si"&gt;)&lt;/span&gt;

docker push team/rakudo:&lt;span class="si"&gt;$(&lt;/span&gt;config &lt;span class="nb"&gt;arch&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;-&lt;span class="si"&gt;$(&lt;/span&gt;config version&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once we've created all necessary files we can navigate to a "build-rakudo" project in Sparky UI and hit "build now" button:&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%2Fpdd5nkaqmjhdvmi7gjn7.jpeg" 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%2Fpdd5nkaqmjhdvmi7gjn7.jpeg" alt=" " width="742" height="732"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By choosing a Rakudo version and Linux distribution and launching a new build, within a few minutes we get a new Rakudo docker image published to an internal docker registry.&lt;/p&gt;




&lt;p&gt;This is just a &lt;em&gt;simple&lt;/em&gt; example of how one can use Sparky for automation, there is more then that, but the main idea is to spin up jobs quickly with simple web interfaces generated from YAML specifications, resulting in various kinds of centralized tools available for needs of your team. &lt;/p&gt;

&lt;p&gt;For the scaling it's even possible to convert scenarios into plain Raku modules or Sparrow plugins and distribute them across many teams.&lt;/p&gt;




&lt;p&gt;Conclusion&lt;/p&gt;

&lt;p&gt;Sparky is versatile task runner enable small teams of developers of self-hosted platform to automate all boring and manual stuff they might have during development cycle, check it out for more details - &lt;a href="https://raku.land/zef:melezhik/Sparky" rel="noopener noreferrer"&gt;https://raku.land/zef:melezhik/Sparky&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ci</category>
      <category>rakulang</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
