<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/assets/xslt/rss.xslt" ?>
<?xml-stylesheet type="text/css" href="/assets/css/rss.css" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title>DevOps Spiral</title>
		<description>Spiral journey over kubernetes, cloud and devops world.</description>
		<link>https://devopsspiral.com/</link>
		<atom:link href="https://devopsspiral.com/feed.xml" rel="self" type="application/rss+xml" />
		
			<item>
				<title>Publishing RobotFramework reports in Azure Blob Storage</title>
				<link>https://devopsspiral.com/articles/robotframework/rf-reports-azureblob/</link>
				<pubDate>Thu, 08 Apr 2021 00:00:00 +0000</pubDate>
				<description>&lt;p&gt;In a long way off goal of making &lt;a href=&quot;https://github.com/devopsspiral/rf-service&quot;&gt;rf-service&lt;/a&gt; covering everything between writing tests and viewing test results I decided to work a little more on where the test reports could  be published. Using managed service for that seems to be perfect choice, especially if you could handle it in a ‘send and forget’ manner. In my work we are using Azure extensively so Azure Blob storage with its static website hosting is what I decided to use. In this article you will find very basic introduction to static web hosting on Azure and how you can send Robot Framework reports there programmatically.&lt;/p&gt;

&lt;div class=&quot;panel radius&quot;&gt;
  &lt;p id=&quot;toc&quot;&gt;&lt;strong&gt;Table of Contents&lt;/strong&gt;&lt;/p&gt;
&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#context&quot; id=&quot;markdown-toc-context&quot;&gt;Context&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#azure-blob-storage-static-website-hosting&quot; id=&quot;markdown-toc-azure-blob-storage-static-website-hosting&quot;&gt;Azure Blob Storage static website hosting&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#creating-blob-storage-and-static-website&quot; id=&quot;markdown-toc-creating-blob-storage-and-static-website&quot;&gt;Creating blob storage and static website&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#changes-in-rf-service&quot; id=&quot;markdown-toc-changes-in-rf-service&quot;&gt;Changes in rf-service&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#sending-reports-to-azure-using-python&quot; id=&quot;markdown-toc-sending-reports-to-azure-using-python&quot;&gt;Sending reports to Azure using Python&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusion&quot; id=&quot;markdown-toc-conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;/div&gt;

&lt;h2 id=&quot;context&quot;&gt;Context&lt;/h2&gt;

&lt;p&gt;This article is a part of series connected with testing on Kubernetes. You can find more info in following articles:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://devopsspiral.com/articles/k8s/robotframework-kubelibrary/&quot;&gt;Robot Framework library for testing Kubernetes&lt;/a&gt; - in this part I’m describing Robot Framework library (Python) that uses Kubernetes client for getting info about your cluster and turning it into actual test suites.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://devopsspiral.com/articles/k8s/robotframework-service/&quot;&gt;Testing on kubernetes - rf-service&lt;/a&gt; - this article describes Python service executed in a form of CronJob that actually runs the tests from KubeLibrary on kubernetes cluster.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://devopsspiral.com/articles/k8s/robotframework-service-fe/&quot;&gt;Intro to Vue.js. Testing on kubernetes - rf-service frontend&lt;/a&gt; - adding frontend to the rf-service.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://devopsspiral.com/articles/k8s/testing-with-octopus/&quot;&gt;Testing with octopus&lt;/a&gt; - using Octopus project for test execution.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h2 id=&quot;azure-blob-storage-static-website-hosting&quot;&gt;Azure Blob Storage static website hosting&lt;/h2&gt;

&lt;p&gt;Azure blob storage is a great way of keeping files in cloud. It’s a simplified abstraction of file system with API allowing to configure things like retention, access control, redundancy etc. It is part of more general concept - storage account - which can be used for services like queues, tables or even big data scenarios like Data Lake storage.&lt;/p&gt;

&lt;h3 id=&quot;creating-blob-storage-and-static-website&quot;&gt;Creating blob storage and static website&lt;/h3&gt;

&lt;p&gt;Creating blob storage is pretty easy, as a prerequisite, you need to have resource group in place, as a container for your storage account. Depending on the requirements you can select performance, account type and proper replication level. I will go with the defaults.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/rf-report-blob/rf-blob-sa.png&quot; alt=&quot;Creating Storage Account V2&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After storage account is ready to be used, just go to Static website and enable the feature. This is exactly the thing that will make Robot Framework html reports to be visible as a website. You don’t need to set index or error document in this case, we won’t be really serving any startpage or anything like this, just the reports. It is good to take note of the primary endpoint, this is the URL under which your content will be served.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/rf-report-blob/static-website.png&quot; alt=&quot;Enabling static website&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Enabling static website will create &lt;em&gt;$web&lt;/em&gt; container, which is kind of root of everything you want to publish.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/rf-report-blob/containers.png&quot; alt=&quot;Web container&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now the important part, by default static websites are public - even though blob storage is by default not accessible publicly the $web container is handled differently (it supposed to be serving website ). You can limit the access to it in different ways, in corporate environment you would probably have private network connecting your work environment with Azure, network of internal/public IPs, etc. There is quick and easy way to limit the access only to your PC though, you can get back to storage account level, go to Networking and allow access from selected networks and use button to add client IP address. This will configure firewall to let in only your IP (remember that this IP may change depending on how exactly you are connecting to the internet).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/rf-report-blob/firewall.png&quot; alt=&quot;Web container&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h2 id=&quot;changes-in-rf-service&quot;&gt;Changes in rf-service&lt;/h2&gt;
&lt;p&gt;Looking at the rf-service changes, simply new publisher called AzureBlobPublisher is available.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;usage: rf-service &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-h&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; INCLUDE_TAGS] &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; EXCLUDE_TAGS]
                  &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--AzureBlobPublisher-connection_string&lt;/span&gt; AZUREBLOBPUBLISHER_CONNECTION_STRING]
                  &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--AzureBlobPublisher-path&lt;/span&gt; AZUREBLOBPUBLISHER_PATH]
                  &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--AzureBlobPublisher-prefix&lt;/span&gt; AZUREBLOBPUBLISHER_PREFIX]
                  &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--AzureBlobPublisher-blob_url&lt;/span&gt; AZUREBLOBPUBLISHER_BLOB_URL]
                  &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--CaddyPublisher-url&lt;/span&gt; CADDYPUBLISHER_URL]
                  &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--LocalPublisher-dest&lt;/span&gt; LOCALPUBLISHER_DEST]
                  &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--LocalFetcher-src&lt;/span&gt; LOCALFETCHER_SRC]
                  &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--ZipFetcher-url&lt;/span&gt; ZIPFETCHER_URL]
                  &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--ZipFetcher-path&lt;/span&gt; ZIPFETCHER_PATH]
                  &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;config_file]

RobotFramework service.

positional arguments:
  config_file           JSON config file

optional arguments:
  &lt;span class=&quot;nt&quot;&gt;-h&lt;/span&gt;, &lt;span class=&quot;nt&quot;&gt;--help&lt;/span&gt;            show this &lt;span class=&quot;nb&quot;&gt;help &lt;/span&gt;message and &lt;span class=&quot;nb&quot;&gt;exit&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; INCLUDE_TAGS, &lt;span class=&quot;nt&quot;&gt;--include&lt;/span&gt; INCLUDE_TAGS
                        Include &lt;span class=&quot;nb&quot;&gt;test &lt;/span&gt;tags
  &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; EXCLUDE_TAGS, &lt;span class=&quot;nt&quot;&gt;--exclude&lt;/span&gt; EXCLUDE_TAGS
                        Exclude &lt;span class=&quot;nb&quot;&gt;test &lt;/span&gt;tags
  &lt;span class=&quot;nt&quot;&gt;--AzureBlobPublisher-connection_string&lt;/span&gt; AZUREBLOBPUBLISHER_CONNECTION_STRING
  &lt;span class=&quot;nt&quot;&gt;--AzureBlobPublisher-path&lt;/span&gt; AZUREBLOBPUBLISHER_PATH
  &lt;span class=&quot;nt&quot;&gt;--AzureBlobPublisher-prefix&lt;/span&gt; AZUREBLOBPUBLISHER_PREFIX
  &lt;span class=&quot;nt&quot;&gt;--AzureBlobPublisher-blob_url&lt;/span&gt; AZUREBLOBPUBLISHER_BLOB_URL
  &lt;span class=&quot;nt&quot;&gt;--CaddyPublisher-url&lt;/span&gt; CADDYPUBLISHER_URL
  &lt;span class=&quot;nt&quot;&gt;--LocalPublisher-dest&lt;/span&gt; LOCALPUBLISHER_DEST
  &lt;span class=&quot;nt&quot;&gt;--LocalFetcher-src&lt;/span&gt; LOCALFETCHER_SRC
  &lt;span class=&quot;nt&quot;&gt;--ZipFetcher-url&lt;/span&gt; ZIPFETCHER_URL
  &lt;span class=&quot;nt&quot;&gt;--ZipFetcher-path&lt;/span&gt; ZIPFETCHER_PATH&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;It has 4 parameters described as:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;connection_string&lt;/strong&gt; - connection string taken from storage account Access keys (see below picture)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;path&lt;/strong&gt; - path in the $web container, can be used for grouping reports&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;prefix&lt;/strong&gt; - prefix for the target report, can be used for test type distinction&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;blob_url&lt;/strong&gt; - entirely informative parameter. Blob url along with path and prefix will write complete report url in logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/rf-report-blob/connection_string.png&quot; alt=&quot;Container string source&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Example test execution with publishing reports in Azure Blob Storage would look similar to:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;./src/scripts/rf-service &lt;span class=&quot;nt&quot;&gt;--AzureBlobPublisher-path&lt;/span&gt; some/path/ &lt;span class=&quot;nt&quot;&gt;--AzureBlobPublisher-prefix&lt;/span&gt; smoke &lt;span class=&quot;nt&quot;&gt;--AzureBlobPublisher-connection-string&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;DefaultEndpointsProtocol=...&apos;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--LocalFetcher-src&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt;/resources/testcases/&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And the report would be published under &lt;a href=&quot;localhost&quot;&gt;https://v2generalpurpose.z20.web.core.windows.net/some/path/smoke15-03-21T175232-662444.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h3 id=&quot;sending-reports-to-azure-using-python&quot;&gt;Sending reports to Azure using Python&lt;/h3&gt;
&lt;p&gt;It is worth to go through the Python code that is responsible for sending the reports in case you want to use it outside the rf-service.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;_publish&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;reportname&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_standard_reportname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;upload_file_path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;/tmp/&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reportname&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;.html&apos;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ResultWriter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write_results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;report&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;upload_file_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                           &lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;blob_service_client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BlobServiceClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;from_connection_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connection_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;container_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;$web&quot;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;target_blob&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}{&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reportname&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;.html&quot;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;blob_client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blob_service_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_blob_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;container_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;target_blob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;static_html_content_settings&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ContentSettings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content_type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;text/html&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;upload_file_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;rb&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;blob_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;upload_blob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;content_settings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;static_html_content_settings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;publish_target&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;blob_url&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;target_blob&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Going from top to bottom, first of all I’m writing Robot Framework report to file which then will be uploaded to Azure Blob storage. Then, there is part containing initialization of blob service client and setting target blob path and name. Remember to set content settings to ‘text/html’ to make sure that report is rendered as static page. Last step is just sending the content and viewing the url of the published report.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Azure blob storage is an interesting option for keeping Robot Framework reports. There are couple of functionalities like retention, tagging, access control that can be configured for your reports but were not described in this article, but even without it, simple publishing in managed service is very convenient. Microsoft makes sure that API for blob storage is accessible through different programming languages like Python, JavaScript, Ruby and others so it can become storage for different kind of automation.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
</description>
				<guid isPermaLink="true">https://devopsspiral.com/articles/robotframework/rf-reports-azureblob/</guid>
			</item>
		
			<item>
				<title>Centralizing Ansible logs using fluentd and Azure</title>
				<link>https://devopsspiral.com/video/ansible-ala/</link>
				<pubDate>Tue, 24 Nov 2020 00:00:00 +0000</pubDate>
				<description>
</description>
				<guid isPermaLink="true">https://devopsspiral.com/video/ansible-ala/</guid>
			</item>
		
			<item>
				<title>Inspecting python tools using eBPF</title>
				<link>https://devopsspiral.com/articles/linux/bcc-tools/</link>
				<pubDate>Wed, 21 Oct 2020 00:00:00 +0000</pubDate>
				<description>&lt;p&gt;After my first article about eBPF (&lt;a href=&quot;https://devopsspiral.com/articles/linux/ebpf-unlock/&quot;&gt;Unlocking eBPF power&lt;/a&gt;), which was just-for-fun type of article, I wanted to start using eBPF in my daily work. I remember the situation from one of my interviews, where I was asked ‘How would I approach unknown binary/script?’. They were asking what tools I could use to get the most info about it and possibly perform some basic troubleshooting. I remember it quite vividly, mainly because I failed on it so badly. After reading this article you should probably be at much better position when dealing with similar type of problem.&lt;/p&gt;

&lt;div class=&quot;panel radius&quot;&gt;
  &lt;p id=&quot;toc&quot;&gt;&lt;strong&gt;Table of Contents&lt;/strong&gt;&lt;/p&gt;
&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#wait-what-is-ebpf-again&quot; id=&quot;markdown-toc-wait-what-is-ebpf-again&quot;&gt;Wait, what is eBPF again?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#do-i-need-to-be-badass-kernel-developer-to-use-ebpf&quot; id=&quot;markdown-toc-do-i-need-to-be-badass-kernel-developer-to-use-ebpf&quot;&gt;Do I need to be badass kernel developer to use eBPF?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#installation&quot; id=&quot;markdown-toc-installation&quot;&gt;Installation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#inspection&quot; id=&quot;markdown-toc-inspection&quot;&gt;Inspection&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#execsnoop&quot; id=&quot;markdown-toc-execsnoop&quot;&gt;execsnoop&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#tcpconnect&quot; id=&quot;markdown-toc-tcpconnect&quot;&gt;tcpconnect&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#opensnoop&quot; id=&quot;markdown-toc-opensnoop&quot;&gt;opensnoop&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#pythoncalls&quot; id=&quot;markdown-toc-pythoncalls&quot;&gt;pythoncalls&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#trace&quot; id=&quot;markdown-toc-trace&quot;&gt;trace&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusion&quot; id=&quot;markdown-toc-conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;/div&gt;

&lt;h2 id=&quot;wait-what-is-ebpf-again&quot;&gt;Wait, what is eBPF again?&lt;/h2&gt;

&lt;p&gt;If you hear eBPF for the first time I would recommend going through (&lt;a href=&quot;https://devopsspiral.com/articles/linux/ebpf-unlock/&quot;&gt;Unlocking eBPF power&lt;/a&gt;), where I’m describing my first contact with eBPF. For quick recap: “eBPF is a functionality of linux kernel that allows lightweight execution of user code as a response to kernel events. The events could be hardware/software events, tracing events both static (compiled into code) and dynamic (attached in runtime), etc. The code itself is limited in a sense that it is guaranteed to finish (no loops) and is verified before loading into kernel.”&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h2 id=&quot;do-i-need-to-be-badass-kernel-developer-to-use-ebpf&quot;&gt;Do I need to be badass kernel developer to use eBPF?&lt;/h2&gt;

&lt;p&gt;NO! And this it what I’ll try to show in this article. Reading some comments regarding eBPF I see people falling into two groups: the ones who doesn’t know eBPF yet and those using it for some serious stuff and describing it with complicated language. This can be very intimidating. Concept of building a bridge between user and kernel space is very powerful and there are many complex projects around eBPF. C language is also very powerfully, but how often do you think about grep or curl internals? Guess what, eBPF has also project called &lt;a href=&quot;https://github.com/iovisor/bcc&quot;&gt;BCC&lt;/a&gt;(BPF Compiler Collection) that has around 100 tools. To be honest BCC is made for writing eBPF programs and the tools part is “just” addition.&lt;/p&gt;

&lt;p&gt;I will be using BCC toolkit for inspecting ansible, which is what I’m focused on at my work currently, but everything described here could be used for any other python based tools and most of it for practically any executable.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h2 id=&quot;installation&quot;&gt;Installation&lt;/h2&gt;
&lt;p&gt;Official packages for Ubuntu seem to be outdated so I followed installation from source as described in &lt;a href=&quot;https://github.com/iovisor/bcc/blob/master/INSTALL.md#ubuntu---source&quot;&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;bison build-essential cmake flex git libedit-dev &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  libllvm6.0 llvm-6.0-dev libclang-6.0-dev python zlib1g-dev libelf-dev

git clone https://github.com/iovisor/bcc.git
&lt;span class=&quot;nb&quot;&gt;mkdir &lt;/span&gt;bcc/build&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;bcc/build
cmake ..
make
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;make &lt;span class=&quot;nb&quot;&gt;install
&lt;/span&gt;cmake &lt;span class=&quot;nt&quot;&gt;-DPYTHON_CMD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;python3 .. &lt;span class=&quot;c&quot;&gt;# build python3 binding&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;pushd &lt;/span&gt;src/python/
make
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;make &lt;span class=&quot;nb&quot;&gt;install
popd&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Since BCC has Python front-end, most of the tools are essentially Python scripts and needs valid PYTHONPATH env variable setting. You can export it once (in. bashrc/.zshrc) to avoid missing modules problems. Assuming that you’ve installed it in your home, you should be fine with below export.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PYTHONPATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$PYTHONPATH&lt;/span&gt;:~/bcc/src/python/&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h2 id=&quot;inspection&quot;&gt;Inspection&lt;/h2&gt;

&lt;h3 id=&quot;execsnoop&quot;&gt;execsnoop&lt;/h3&gt;
&lt;p&gt;To be perfectly honest ansible execution in my case is wrapped in a script that does some extra steps. I don’t know what steps exactly, but this can be checked using execsnoop which traces all new processes.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;./the_ansible_wrapper.sh &amp;amp; &lt;span class=&quot;nb&quot;&gt;sudo&lt;/span&gt; ~/bcc/tools/execsnoop.py
PCOMM            PID    PPID   RET ARGS
git              2345   2333     0 /usr/bin/git checkout develop
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;…&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
ansible-galaxy   2333   2310     0 ansible-galaxy &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; ansible/playbooks/requirements.yml &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;…&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
ansible-playboo  2344   2331     0 ansible-playbook &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; inventory.py some_playbook.yml
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;…&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
python3.9        2356   2351     0 /usr/local/bin/python3.9 inventory.py &lt;span class=&quot;nt&quot;&gt;--list&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;…&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You can easily get some info about what is happening, there is repository checkout, ansible roles download, playbook execution and it is using dynamic inventory in form of script. I’ve trimmed the output on purpose to hide some of the exact values, but with full output you would get repo names, executable versions, full paths to files and all other good stuff.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h3 id=&quot;tcpconnect&quot;&gt;tcpconnect&lt;/h3&gt;
&lt;p&gt;Don’t you have this uncomfortable thought for a second that whenever you install something new on your computer there is a chance that someone is gathering some info about you and sending it out?&lt;/p&gt;

&lt;p&gt;Well, you can have similar feeling whenever you are working on a code that is not yet known to you. Ansible is all about handling configuration of remote systems, but target inventory is not the only system it connects to. Playbooks can first gather input from configuration store, secret store, infrastructure, etc. You can track all of those using tcpconnect. I changed the IPs in the output to some meaningful names.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo&lt;/span&gt; ~/bcc/tools/tcpconnect.py &amp;amp;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;./the_ansible_wrapper.sh &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /dev/null
ID    COMM               FD ERR PATH
PID    COMM         IP SADDR            DADDR            DPORT
6560   the_ansible_wrapper.sh   4  my_machine     vault    443 
6569   ssh          4  my_machine     github.com     22  
6586   ssh          4  my_machine     github.com     22
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h3 id=&quot;opensnoop&quot;&gt;opensnoop&lt;/h3&gt;
&lt;p&gt;So we know what is going on under the hood of the tool, where it is connecting to, it would be great to know which files are being used. This is exactly what opensnoop is doing. Not sure which ansible.cfg is effectively used? - use opensnoop. Looking for some variable source files? - use opensnoop. Or maybe you are just refactoring your ansible code and trying to get rid of some unused roles and plays - by seeing which files are read you can easily see what is left.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;./the_ansible_wrapper.sh &amp;amp; &lt;span class=&quot;nb&quot;&gt;sudo&lt;/span&gt; ~/bcc/tools/opensnoop.py &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; ansible-pl
ID    COMM               FD ERR PATH
15638  ansible-playboo     3   0 /tmp/ansible_pdrczx2p/vault.json
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
15638  ansible-playboo     6   0 /tmp/ansible_pdrczx2p/ansible/group_vars/all.yaml
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
15638   ansible-playboo     4   0 /home/ansible/.ansible.cfg
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;It is worth noting that you might observe limitations of some of those tools that manifests itself with entries like “Possibly lost X samples” this is sign that there are too many data coming into the script from BPF and user space script is not keeping up. Usually it happens when you started tracing too many calls, try limiting the tool to some specific PID or process name. If it is still too much the only option is to filter items on BPF side. The scripts has BPF snippets defined as strings so you can try editing it after some trial and error. I did that for opensnoop to limit the files to specific directory that contained all the playbooks. See the commented lines below.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;trace_return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pt_regs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;u64&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bpf_get_current_pid_tgid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val_t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;valp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{};&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// defining target directory&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// const char searchString[] = &quot;/tmp/ansible_&quot;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;u64&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tsp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bpf_ktime_get_ns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;valp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;infotmp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lookup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;valp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// missed entry&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;bpf_probe_read_kernel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;comm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;sizeof&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;comm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;valp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;comm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;bpf_probe_read_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;sizeof&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;valp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// comparing two strings if they don&apos;t match leave the function&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// if (memcmp(&amp;amp;data.fname, searchString,8) != 0) {&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;//    return 0;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// }&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h3 id=&quot;pythoncalls&quot;&gt;pythoncalls&lt;/h3&gt;
&lt;p&gt;Sometimes you want to get a little more from the underlying code. BCC tools has the ucalls utility that can track static tracing markers for different high-level languages including python (pythoncalls is a wrapper for ucalls). It allows tracking number of calls to the function and its latency. The problem is that you need to have python build from source with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--with-dtrace&lt;/code&gt; flag to add USDT (User Statically Defined Trace) probes. Bear with me, it is not that complicated.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;systemtap-sdt-dev
wget https://www.python.org/ftp/python/3.9.0/Python-3.9.0.tgz
&lt;span class=&quot;nb&quot;&gt;tar&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-xf&lt;/span&gt; Python-3.9.0.tgz
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;Python-3.9.0/
./configure ––enable–optimizations &lt;span class=&quot;nt&quot;&gt;--with-dtrace&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# we want to install it locally next to existing Python&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;make altinstall&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You can verify if everything is fine by using another BCC tool called tplist, which displays kernel tracepoints and USDT probes that we just enabled. Ucalls is using function__entry and function__return probes.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;~/bcc/tools/tplist.py &lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt; python3.9
/usr/local/bin/python3.9 python:line
/usr/local/bin/python3.9 python:function__entry
/usr/local/bin/python3.9 python:function__return
/usr/local/bin/python3.9 python:import__find__load__start
/usr/local/bin/python3.9 python:import__find__load__done
/usr/local/bin/python3.9 python:audit
/usr/local/bin/python3.9 python:gc__start
/usr/local/bin/python3.9 python:gc__done&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now, when you start your script again you should see nice statistics of used functions. There are different flags that you can use with ucalls, in the following example I just used simple call count and latency.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;./the_ansible_wrapper.sh &amp;amp; &lt;span class=&quot;nb&quot;&gt;sudo&lt;/span&gt; ~/bcc/tools/pythoncalls.sh &lt;span class=&quot;nt&quot;&gt;-Lm&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$!&lt;/span&gt;
METHOD                                              &lt;span class=&quot;c&quot;&gt;# CALLS TIME (ms)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
/usr/local/lib/python3.9/shutil.py.rmtree                 1  65.35
/usr/local/lib/python3.9/logging/__init__.py.makeRecord     1089  66.35
/usr/local/lib/python3.9/ssl.py.read                      1  79.20
/usr/local/lib/python3.9/ssl.py.recv_into                 1  79.22
/usr/local/lib/python3.9/socket.py.readinto               1  79.24
/usr/local/lib/python3.9/http/client.py._read_status        1  79.27
/usr/local/lib/python3.9/http/client.py.begin             1  79.85
/usr/local/lib/python3.9/http/client.py.getresponse        1  79.94
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h3 id=&quot;trace&quot;&gt;trace&lt;/h3&gt;
&lt;p&gt;Last thing that is missing from python script inspection perspective are the environment variables. The thing is a bit tricky, because there are possibly many ways programs are getting environment variables from the system. For C based tools it is usually getenv function from libc. So you could trace it using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ltrace -e getenv &amp;lt;script&amp;gt;&lt;/code&gt;, or I should rather say &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/bcc/tools/trace.py &apos;r:c:getenv &quot;%s=%s&quot;, arg1, retval&apos;&lt;/code&gt; to trace return probes (r:) of the libc library (c:) for function getenv. We want to print first argument and return value of the function which corresponds to env var name and value - it might need some tweaking as the output is not fitting into &lt;em&gt;trace&lt;/em&gt; output columns but you get the idea. Python is handling those differently and it loads the env variables into dictionary kept in special _Environ object.&lt;/p&gt;

&lt;p&gt;If we are already building Python with USDT probes why not add another one dynamically to os.py module. I will use &lt;a href=&quot;https://github.com/sthima/libstapsdt&quot;&gt;libstapsdt&lt;/a&gt; and python wrapper for it, called &lt;a href=&quot;https://github.com/sthima/python-stapsdt&quot;&gt;python-stapsdt&lt;/a&gt;. This library is dynamically creating shared library which is exposing USDT probes so that tools like &lt;em&gt;trace&lt;/em&gt; can see markers to which it can attach to. The code that needs to be added to os.py (/usr/local/lib/pyhon3.9/os.py in my case) can be found below.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;importlib.util&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;spec&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;importlib&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;util&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;spec_from_file_location&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;stapsdt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/home/ansible/.local/lib/python3.9/site-packages/stapsdt.py&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;stapsdt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;importlib&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;util&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;module_from_spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exec_module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stapsdt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;provider&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stapsdt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Provider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;pythonapp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;probe&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;provider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_probe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;getenvProbe&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stapsdt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ArgTypes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uint64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stapsdt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ArgTypes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uint64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;provider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;_Environ&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MutableMapping&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;encodekey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;decodekey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;encodevalue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;decodevalue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;encodekey&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;encodekey&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;decodekey&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;decodekey&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;encodevalue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;encodevalue&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;decodevalue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;decodevalue&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getenv_probe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;encodekey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;PYTHON_INSPECT&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;probe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;is_enabled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
                &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;time&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;encodekey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;probe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fire&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;decodevalue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;KeyError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;probe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fire&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__getitem__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getenv_probe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I’m importing stapsdt module, creating getenvProbe and running firing function as first thing in env variable dictionary getter. So whenever python code is trying to read env variable my probe will be fired. The extra &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;while(not probe.is_enabled):&lt;/code&gt; loop allows waiting for the tracer to attach to the probe whenever PYTHON_INSPECT env variable is set. This is needed because environment variables are usually read as first thing and often BCC trace tool is not quick enough and ends up attaching after all the variables were already checked. Now you can see all the possible env variables and their values if set.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;./the_ansible_wrapper.sh &amp;amp; &lt;span class=&quot;nb&quot;&gt;sudo&lt;/span&gt; ~/bcc/tools/trace.py &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$!&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;u::getenvProbe &quot;%s=%s&quot;, arg1, arg2&apos;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
6686    6686    ansible-playboo getenvProbe      &lt;span class=&quot;nv&quot;&gt;ANSIBLE_SKIP_TAGS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
6686    6686    ansible-playboo getenvProbe      &lt;span class=&quot;nv&quot;&gt;ANSIBLE_USE_PERSISTENT_CONNECTIONS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
6686    6686    ansible-playboo getenvProbe      &lt;span class=&quot;nv&quot;&gt;ANSIBLE_PRECEDENCE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
6686    6686    ansible-playboo getenvProbe      &lt;span class=&quot;nv&quot;&gt;ANSIBLE_YAML_FILENAME_EXT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
6686    6686    ansible-playboo getenvProbe      &lt;span class=&quot;nv&quot;&gt;ANSIBLE_NETCONF_SSH_CONFIG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
6686    6686    ansible-playboo getenvProbe      &lt;span class=&quot;nv&quot;&gt;ANSIBLE_STRING_CONVERSION_ACTION&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
6686    6686    ansible-playboo getenvProbe      &lt;span class=&quot;nv&quot;&gt;ANSIBLE_VERBOSE_TO_STDERR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
6686    6686    ansible-playboo getenvProbe      &lt;span class=&quot;nv&quot;&gt;LANG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;C.UTF-8
6686    6686    ansible-playboo getenvProbe      &lt;span class=&quot;nv&quot;&gt;PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/home/ansible/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Hope that I was able to show how it is easy to use eBPF in your daily work. Obviously there are a lot more tools out there and writing your own script using BCC is probably the next step. Adding USDT probes in to the code opens the door to many possibilities regarding observability, audit and more - definitely need to think of little project regarding that.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
</description>
				<guid isPermaLink="true">https://devopsspiral.com/articles/linux/bcc-tools/</guid>
			</item>
		
			<item>
				<title>CircleCI setup for Python project</title>
				<link>https://devopsspiral.com/video/circleci-python/</link>
				<pubDate>Mon, 05 Oct 2020 00:00:00 +0000</pubDate>
				<description>
</description>
				<guid isPermaLink="true">https://devopsspiral.com/video/circleci-python/</guid>
			</item>
		
			<item>
				<title>K3d/K3s in CircleCI</title>
				<link>https://devopsspiral.com/articles/linux/k3d-orb/</link>
				<pubDate>Wed, 23 Sep 2020 00:00:00 +0000</pubDate>
				<description>&lt;p&gt;I was trying to setup CI (CircleCI) for one of my repositories (&lt;a href=&quot;https://github.com/devopsspiral/KubeLibrary&quot;&gt;KubeLibrary&lt;/a&gt;) and I needed k8s cluster to run test examples. I immediately wanted to setup k3d/k3s cluster, but it turned out not that easy as I thought. I ended up creating k3d orb which allows creating temporary k8s cluster just for tests. In this article I’m describing some of the internals of this orb implemented to fit into CircleCI. If you just want to use it follow orb &lt;a href=&quot;https://circleci.com/orbs/registry/orb/devopsspiral/k3d&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;panel radius&quot;&gt;
  &lt;p id=&quot;toc&quot;&gt;&lt;strong&gt;Table of Contents&lt;/strong&gt;&lt;/p&gt;
&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#background&quot; id=&quot;markdown-toc-background&quot;&gt;Background&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#circleci-docker-setup&quot; id=&quot;markdown-toc-circleci-docker-setup&quot;&gt;CircleCI docker setup&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#orb-design&quot; id=&quot;markdown-toc-orb-design&quot;&gt;Orb design&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#helpers&quot; id=&quot;markdown-toc-helpers&quot;&gt;Helpers&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#kubeconfigs&quot; id=&quot;markdown-toc-kubeconfigs&quot;&gt;Kubeconfigs&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#k3d-cluster&quot; id=&quot;markdown-toc-k3d-cluster&quot;&gt;K3d cluster&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#versions&quot; id=&quot;markdown-toc-versions&quot;&gt;Versions&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#reaching-kubernetes-api&quot; id=&quot;markdown-toc-reaching-kubernetes-api&quot;&gt;Reaching kubernetes API&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#running-your-own-containers-in-the-cluster&quot; id=&quot;markdown-toc-running-your-own-containers-in-the-cluster&quot;&gt;Running your own containers in the cluster&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#exposing-services&quot; id=&quot;markdown-toc-exposing-services&quot;&gt;Exposing services&lt;/a&gt;        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#expose-via-kubectl-proxy&quot; id=&quot;markdown-toc-expose-via-kubectl-proxy&quot;&gt;Expose via kubectl proxy&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#expose-via-load-balancer&quot; id=&quot;markdown-toc-expose-via-load-balancer&quot;&gt;Expose via load balancer&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#expose-via-ingress&quot; id=&quot;markdown-toc-expose-via-ingress&quot;&gt;Expose via ingress&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#publishing-your-own-orb&quot; id=&quot;markdown-toc-publishing-your-own-orb&quot;&gt;Publishing your own orb&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;/div&gt;

&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;
&lt;p&gt;I was already writing about &lt;a href=&quot;https://github.com/rancher/k3d&quot;&gt;K3d&lt;/a&gt; in &lt;a href=&quot;https://devopsspiral.com/articles/k8s/k3d-skaffold/&quot;&gt;this&lt;/a&gt; article and I’m using it on daily basis for quick tests or development. Shortly speaking K3d is dockerized &lt;a href=&quot;https://github.com/rancher/k3s&quot;&gt;K3s&lt;/a&gt; which in turn is a small (but certified) k8s distribution made by Rancher Labs.&lt;/p&gt;

&lt;p&gt;Starting a cluster is just creating containers with k8s nodes, so it shouldn’t be that difficult to make it work in docker environment supported by CircleCI. I’ve quickly learned that fitting k3d into CircleCI needs some gymnastics so I decided to create CircleCI orb to possibly make others people lives easier.&lt;/p&gt;

&lt;p&gt;CircleCI orb is just a way of packaging custom jobs, commands and executors. It doesn’t differ much from regular CI definition in .circleci/config.yml but it is allowing to hide some of the complexity.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h2 id=&quot;circleci-docker-setup&quot;&gt;CircleCI docker setup&lt;/h2&gt;
&lt;p&gt;CircleCI is using remote docker environment, initialized by execution of &lt;em&gt;setup_remote_docker&lt;/em&gt; command. Whatever docker command is used it will be executed on a separate vm, where the only interface is docker command itself. As a result, it has couple of implications regarding networking and file transfer - simply speaking, if you want to run anything towards docker containers it should be also running in a container.&lt;/p&gt;

&lt;p&gt;On top of that, separate steps executions are not sharing environment variables so there is no direct way of passing values outside the CircleCI command. One of the options is to simply write and read values between the commands.&lt;/p&gt;

&lt;p&gt;It is also worth mentioning that remote docker vm has its own resource limits. According to &lt;a href=&quot;https://circleci.com/docs/2.0/building-docker-images/#specifications&quot;&gt;documentation&lt;/a&gt; it is 2xIntel(R) Xeon(R) @ 2.3GHz CPU, 8GB RAM and 100GB disk space. It is not too much, but should be enough to cover some of the scenarios and fits into K3s small deployment size (see &lt;a href=&quot;https://rancher.com/docs/k3s/latest/en/installation/installation-requirements/#cpu-and-memory&quot;&gt;docs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h2 id=&quot;orb-design&quot;&gt;Orb design&lt;/h2&gt;

&lt;h3 id=&quot;helpers&quot;&gt;Helpers&lt;/h3&gt;
&lt;p&gt;To address some of the mentioned limitation and keep k8s experience similar to what you have on your local system I had to create helpers for kubectl and helm. They are all executed as docker containers running in k3d load balancer network to allow reaching kubernetes API on 0.0.0.0 for simplicity.&lt;/p&gt;

&lt;p&gt;Additionally to achieve persistency between steps I used couple of volumes for holding helm cache, config and data. Each kubectl and helm execution has also repository content mounted at /repo/ to allow easy application of YAML files or helm charts.&lt;/p&gt;

&lt;p&gt;All the setup is done in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k3d/k3d-helpers&lt;/code&gt; which should be executed before first k3d cluster is created. This the place where the volumes are initialized.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;docker create &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; /.kube &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; kubeconfig alpine:3.4 /bin/true
docker create &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; /repo &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; repo alpine:3.4 /bin/true
docker &lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; repo:/repo/
docker volume create  helm_cache
docker volume create  helm_config
docker volume create  helm_data
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;To hide kubectl and helm internals they both have wrappers implemented as bash functions kept in helpers.sh, which is then sourced in every &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k3d/k3d-run&lt;/code&gt; command.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&amp;lt;&lt;/span&gt;&amp;lt;EOF &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; helpers.sh
        &lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;K3D_USING_HELPERS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true
        export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;K3D_CLUSTER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;NotSet
        &lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;K3D_KUBECONFIG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;NotSet
        kubectl &lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
          docker run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; kubectl &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;KUBECONFIG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\$&lt;/span&gt;K3D_KUBECONFIG &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;--network&lt;/span&gt; container:k3d-&lt;span class=&quot;se&quot;&gt;\$&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;K3D_CLUSTER&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-serverlb&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;--volumes-from&lt;/span&gt; kubeconfig &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;--volumes-from&lt;/span&gt; repo &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
          bitnami/kubectl:&lt;span class=&quot;se&quot;&gt;\$&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;KUBECTL_VERSION:-latest&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\$&lt;/span&gt;@
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h3 id=&quot;kubeconfigs&quot;&gt;Kubeconfigs&lt;/h3&gt;

&lt;p&gt;Kubeconfigs are treated very similar to other data, each new cluster writes a file into /.kube/&amp;lt;name of cluster&amp;gt; on separate volume, which is also mounted into kubectl and helm containers. Path to kubeconfig is passed as &lt;em&gt;KUBECONFIG&lt;/em&gt; environment variable. Switching of target cluster is done by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k3d/k3d-use&lt;/code&gt; command that changes the value of &lt;em&gt;K3D_KUBECONFIG&lt;/em&gt; globally. Creating new cluster always sets the KUBECONFIG to itself.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h3 id=&quot;k3d-cluster&quot;&gt;K3d cluster&lt;/h3&gt;

&lt;p&gt;New k8s cluster is created using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k3d/k3d-up&lt;/code&gt; command that accepts following parameters:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;cluster-name&lt;/em&gt; - name of the cluster, used for determining kubeconfig, but also to point to correct cluster load balancer.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;k3s-version&lt;/em&gt; - tag of the k3s container, used to select kubernetes version i.e.: v1.16.14-k3s1&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;agents&lt;/em&gt; - number of kubernetes agents, default: 1.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h3 id=&quot;versions&quot;&gt;Versions&lt;/h3&gt;

&lt;p&gt;Apart from kubernetes cluster API version, both kubectl and helm can be used in specific versions. It is configured by setting environment variables: &lt;em&gt;KUBECTL_VERSION&lt;/em&gt; and &lt;em&gt;HELM_VERSION&lt;/em&gt;. They can be set for particular step or globally for the job:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;jobs&lt;/span&gt;:
    test-k3d-versions:
      executor: k3d/default
      environment:
        - KUBECTL_VERSION: 1.16.15
        - HELM_VERSION: 3.3.1
      steps:
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h3 id=&quot;reaching-kubernetes-api&quot;&gt;Reaching kubernetes API&lt;/h3&gt;

&lt;p&gt;To reach kubernetes API directly it is best to place the container in the &lt;em&gt;k3d-${K3D_CLUSTER}-serverlb&lt;/em&gt; network, this way the API is exposed on 0.0.0.0:6443. It is exactly how kubectl and helm containers are used.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        helm &lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
          docker run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; helm &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;KUBECONFIG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\$&lt;/span&gt;K3D_KUBECONFIG &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;--network&lt;/span&gt; container:k3d-&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;K3D_CLUSTER&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-serverlb&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;When k3d creates cluster it assigns random port that is forwarding traffic to 6443 and that is what is landing in kubeconfig eventually. Consequently, small change to cluster.server URL needs to be applied for each of the kubeconfig files to point to original 0.0.0.0:6443.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h3 id=&quot;running-your-own-containers-in-the-cluster&quot;&gt;Running your own containers in the cluster&lt;/h3&gt;

&lt;p&gt;As I already said, anything that is supposed to target k3d cluster should run in the container, but what if the container is supposed to run in the cluster itself? Own container registry would be perfect, but it is not yet part of the orb. Fortunately, there is simpler approach introduced by k3d called image import. You can simply copy image that you just build into the cluster container cache like in the example below.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;docker build &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; pyserver &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; sample/own_image/Dockerfile &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
k3d image import &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;K3D_CLUSTER&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; pyserver:latest&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;It is important to remember that to use container cache &lt;em&gt;imagePullPolicy&lt;/em&gt; in kubernetes resources cannot be set to &lt;em&gt;Always&lt;/em&gt; which enforces checking newer image in the registry. Example deployment would look similar to following code.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;  &lt;span class=&quot;na&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;pyserver&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;containers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;pyserver&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;imagePullPolicy&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Never&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h3 id=&quot;exposing-services&quot;&gt;Exposing services&lt;/h3&gt;

&lt;p&gt;After deploying your own workloads on the cluster it would be good to also know how to expose them to the outside world. In current k3d orb setup you can use at least 3 ways to achieve this. In all the examples I’m using Grafana chart as a target service and reaching it from busybox wget.&lt;/p&gt;

&lt;h4 id=&quot;expose-via-kubectl-proxy&quot;&gt;Expose via kubectl proxy&lt;/h4&gt;
&lt;p&gt;As a first option I will describe kubectl proxy which should handle all the networking complexity and expose the pod port on 0.0.0.0. In the k3d orb it is achieved by first getting pod name with kubectl and then using &lt;em&gt;proxy&lt;/em&gt; command which is just kubectl in detached container. To reach target port you just need to deploy container in proxy container network as shown below (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--network container:proxy&lt;/code&gt;).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;- k3d/k3d-run:
    step-name: Expose service via kubectl proxy
    &lt;span class=&quot;nb&quot;&gt;command&lt;/span&gt;: |
      helm repo add stable https://kubernetes-charts.storage.googleapis.com
      helm &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;grafana stable/grafana
      &lt;span class=&quot;nb&quot;&gt;sleep &lt;/span&gt;30
      &lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;POD_NAME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;kubectl get pods &lt;span class=&quot;nt&quot;&gt;--namespace&lt;/span&gt; default &lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;app.kubernetes.io/name=grafana,app.kubernetes.io/instance=grafana&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;jsonpath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;{.items[0].metadata.name}&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
      proxy &lt;span class=&quot;nt&quot;&gt;--namespace&lt;/span&gt; default port-forward &lt;span class=&quot;nv&quot;&gt;$POD_NAME&lt;/span&gt; 3000
      docker run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--network&lt;/span&gt; container:proxy busybox /bin/sh &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;wget 0.0.0.0:3000; cat index.html&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep &lt;/span&gt;Grafana&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h4 id=&quot;expose-via-load-balancer&quot;&gt;Expose via load balancer&lt;/h4&gt;
&lt;p&gt;Another option is to use kubernetes service type LoadBalancer that will forward port on k3d-${K3D_CLUSTER}-serverlb container and forward it to each agent/node and to target pod eventually. SERVICE_IP is set to load balancer container and to be able to reach it we need to put our container (the one running the wget) in the k3d cluster network using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--network k3d-${K3D_CLUSTER}&lt;/code&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;- k3d/k3d-run:
    step-name: Expose service via loadbalancer
    &lt;span class=&quot;nb&quot;&gt;command&lt;/span&gt;: |
      helm repo add stable https://kubernetes-charts.storage.googleapis.com
      &lt;span class=&quot;c&quot;&gt;# Notice repo content available at /repo/ automatically&lt;/span&gt;
      helm &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;grafana stable/grafana &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; /repo/sample/grafana/with_lb.yaml
      &lt;span class=&quot;nb&quot;&gt;sleep &lt;/span&gt;30
      &lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;SERVICE_IP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;kubectl get svc &lt;span class=&quot;nt&quot;&gt;--namespace&lt;/span&gt; default grafana &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;jsonpath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;{.status.loadBalancer.ingress[0].ip}&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;c&quot;&gt;# Test if Grafana login page is reachable, exit code 1 means grep didn&apos;t found match&lt;/span&gt;
      docker run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--network&lt;/span&gt; k3d-&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;K3D_CLUSTER&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; busybox /bin/sh &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;wget &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;SERVICE_IP&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;:3000; cat index.html&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep &lt;/span&gt;Grafana&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h4 id=&quot;expose-via-ingress&quot;&gt;Expose via ingress&lt;/h4&gt;
&lt;p&gt;The last option is to expose containers using ingress. K3s/K3d is equipped with Traefik ingress controller, so the only missing part to start using it is to create ingress resource. In my case it is already handled by the Grafana chart, but it should be fairly easy to reproduce it. The way it works is not that different than load balancer way, there is just extra piece (Traefik) routing by URLs instead of ports. Notice adding the hostname in /etc/hosts of the container in below example.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;- k3d/k3d-run:
    step-name: Expose service via ingress
    &lt;span class=&quot;nb&quot;&gt;command&lt;/span&gt;: |
      helm repo add stable https://kubernetes-charts.storage.googleapis.com
      &lt;span class=&quot;c&quot;&gt;# Notice repo content available at /repo/ automatically&lt;/span&gt;
      helm &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;grafana stable/grafana &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; /repo/sample/grafana/with_ingress.yaml
      &lt;span class=&quot;nb&quot;&gt;sleep &lt;/span&gt;45
      &lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;INGRESS_IP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;kubectl get ing &lt;span class=&quot;nt&quot;&gt;--namespace&lt;/span&gt; default grafana &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;jsonpath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;{.status.loadBalancer.ingress[0].ip}&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;c&quot;&gt;# Test if Grafana login page is reachable, exit code 1 means grep didn&apos;t found match&lt;/span&gt;
      docker run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--network&lt;/span&gt; k3d-&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;K3D_CLUSTER&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; busybox &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
      /bin/sh &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;echo &apos;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;INGRESS_IP&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;  chart-example.local&apos; &amp;gt;&amp;gt; /etc/hosts; &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;
      wget http://chart-example.local/; cat index.html&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep &lt;/span&gt;Grafana&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h2 id=&quot;publishing-your-own-orb&quot;&gt;Publishing your own orb&lt;/h2&gt;
&lt;p&gt;As a sort of conclusion, I’d like to put together steps to publish your own orb. The docs are there but I felt a bit lost with all of those at the point where I knew what the content should be but not knowing how to make orb out of it.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Write the steps as without using orb, as for any CI definition in .circleci/config.yml&lt;/li&gt;
  &lt;li&gt;Move the generic orb commands to &lt;a href=&quot;https://circleci.com/docs/2.0/orb-author/#writing-inline-orbs&quot;&gt;inline orb&lt;/a&gt; definition (still in your .circleci/config.yml). Notice that you might use the orb as it was already published as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k3d/&amp;lt;command&amp;gt;&lt;/code&gt;. Test and fix.&lt;/li&gt;
&lt;/ol&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2.1&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;orbs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;python&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;circleci/python@0.3.2&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;k3d&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;commands&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;k3d-up&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Starts k8s (k3d/k3s) cluster&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;cluster-name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Name for the k3d cluster&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;string&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;myk3d&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;(...)&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;test-on-k8s&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;executor&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;python/default&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;setup_remote_docker&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;checkout&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;k3d/k3d-helpers&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;k3d/k3d-up&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;cluster-name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;circleci-k8s-1&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ol start=&quot;3&quot;&gt;
  &lt;li&gt;Install CircleCI CLI as described &lt;a href=&quot;https://circleci.com/docs/2.0/creating-orbs/#step-1---set-up-the-circleci-cli&quot;&gt;here&lt;/a&gt; you would need CircleCI API token.&lt;/li&gt;
  &lt;li&gt;Use &lt;a href=&quot;https://github.com/CircleCI-Public/orb-starter-kit&quot;&gt;orb-starter-kit&lt;/a&gt; for templating a new repository with your orb. The repository name needs to match orb name, but you can change the repository name afterwards without problems, just update origin with something like: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git remote set-url origin https://hostname/USERNAME/REPOSITORY.git&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Migrate what you’ve done till now to new repository. You are no longer keeping things as yaml branches but rather as paths in src folder. The CI definition is in place, you are working on Alpha branch, which works as a staging/develop branch - pull requests into masters are triggering publishing the orb. So you are safe to just play with this branch until you are happy with the code. In my case I made all the examples also as integration tests, so that each commit triggers verification of the examples.&lt;/li&gt;
  &lt;li&gt;Verify your orb against &lt;a href=&quot;https://circleci.com/docs/2.0/orbs-best-practices/#orb-best-practices-guidelines&quot;&gt;orb best practices &lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Remember to update Readme.md, badges links and other parts that are still not fitted to your orb.&lt;/li&gt;
  &lt;li&gt;You can use existing orb repos to see how it works i.e. &lt;a href=&quot;https://github.com/CircleCI-Public/python-orb&quot;&gt;python orb&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Create first PR into master, which will also publish the orb automatically. Just use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[semver:patch|minor|major|skip]&lt;/code&gt; in name of the PR merge commit. So &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[semver:patch]&lt;/code&gt; would publish version 0.0.1, another commit with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[semver:patch]&lt;/code&gt; will publish 0.0.2, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[semver:minor]&lt;/code&gt; should publish 0.1.0 which you could treat as starting point, and so on and so forth.&lt;/li&gt;
  &lt;li&gt;Your Alpha branch may look messy at this point, what you could do is just remove Alpha (after it was merged to master) and create new one out of master. Subsequent commits would probably have small changes or be PRs to Alpha.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Other links:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://circleci.com/docs/2.0/creating-orbs/&quot;&gt;Publishing Orbs&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://circleci.com/blog/how-to-write-an-orb-that-people-will-love-to-use/&quot;&gt;How to write an orb that people will love to use&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
</description>
				<guid isPermaLink="true">https://devopsspiral.com/articles/linux/k3d-orb/</guid>
			</item>
		
			<item>
				<title>Unlocking eBPF power</title>
				<link>https://devopsspiral.com/articles/linux/ebpf-unlock/</link>
				<pubDate>Tue, 08 Sep 2020 00:00:00 +0000</pubDate>
				<description>&lt;p&gt;I heard “eBPF” so many times in recent days that I’ve decided to give it a try. I have very limited knowledge about kernel tracing so I thought it is good opportunity to learn something new. One particular &lt;a href=&quot;https://youtu.be/16slh29iN1g&quot;&gt;talk&lt;/a&gt; (by Brendan Gregg) especially caught my attention and I recommend it if you want to get some general idea behind eBPF. At the beginning of his talk he is showing how he was able to monitor WiFi signal strength with eBPF. I seemed so easy that I wanted to mimic something similar. In this article I’m describing how I used eBPF tracing for locking and suspending my laptop using Bluetooth on my phone.&lt;/p&gt;

&lt;div class=&quot;panel radius&quot;&gt;
  &lt;p id=&quot;toc&quot;&gt;&lt;strong&gt;Table of Contents&lt;/strong&gt;&lt;/p&gt;
&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#so-what-is-really-ebpf&quot; id=&quot;markdown-toc-so-what-is-really-ebpf&quot;&gt;So what is really eBPF?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#bluetooth-case&quot; id=&quot;markdown-toc-bluetooth-case&quot;&gt;Bluetooth case&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#into-the-ebpf&quot; id=&quot;markdown-toc-into-the-ebpf&quot;&gt;Into the eBPF&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#system-lock-and-suspend-handling&quot; id=&quot;markdown-toc-system-lock-and-suspend-handling&quot;&gt;System lock and suspend handling&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusion&quot; id=&quot;markdown-toc-conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;/div&gt;

&lt;h2 id=&quot;so-what-is-really-ebpf&quot;&gt;So what is really eBPF?&lt;/h2&gt;
&lt;p&gt;There are many good sources to explain it along with the history of BPF (some of them you will find in this article), so I will try to make it simple.&lt;/p&gt;

&lt;p&gt;eBPF is a functionality of linux kernel that allows lightweight execution of user code as a response to kernel events. The events could be hardware/software events, tracing events both static (compiled into code) and dynamic (attached in runtime), etc. The code itself is limited in a sense that it is guaranteed to finish (no loops) and is verified before loading into kernel.&lt;/p&gt;

&lt;p&gt;Some facts and how others describes it:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;“Function-as-a-Service for kernel events” (Dan Wendlandt from his &lt;a href=&quot;https://youtu.be/7PXQB-1U380&quot;&gt;talk&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;“Superpowers are coming to Linux” (Brendan Gregg from hist &lt;a href=&quot;https://www.facebook.com/watch/?v=1693888610884236&amp;amp;extid=gOZRPKYFuuClz71r&quot;&gt;talk&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Alternative for service mesh &lt;a href=&quot;https://youtu.be/Wocn6DK3FfM&quot;&gt;Webinar: Comparing eBPF and Istio/Envoy for Monitoring Microservice Interactions&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Cilium which uses eBPF as a foundation, was recently announced GKE networking dataplane.&lt;/li&gt;
  &lt;li&gt;kubectl has trace plugin for scheduling bpftrace programs&lt;/li&gt;
  &lt;li&gt;Android has eBPF loader&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h2 id=&quot;bluetooth-case&quot;&gt;Bluetooth case&lt;/h2&gt;

&lt;p&gt;At the beginning the only thing I knew is that I need to trace Bluetooth. But how should I know what are the function names that are called? Recalling some basic knowledge about linux kernel: kernel is the interface between user programs and hardware &amp;gt; kernel uses kernel modules that are specific for particular hardware &amp;gt; I should probably find Bluetooth module then. So by listing loaded modules I could find at least some anchor to begin with.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;lsmod
Module                  Size  Used by
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
bluetooth             573440  117 btrtl,btintel,btbcm,bnep,btusb,rfcomm&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Shockingly enough, the module I was looking for is named Bluetooth :). Turns out that with this information we can already limit tracing to specific module using trace-cmd. It is a wrapper around ftrace tracer, which in raw form requires writing values into filesystem. You can install it using something similar to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apt-get install -y trace-cmd&lt;/code&gt;. Then you can actually use it for tracing functions of a specific module.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;trace-cmd record &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;:mod:bluetooth&apos;&lt;/span&gt;
  plugin &lt;span class=&quot;s1&quot;&gt;&apos;function&apos;&lt;/span&gt;
Hit Ctrl^C to stop recording
^CCPU0 data recorded at &lt;span class=&quot;nv&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0x671000
    4096 bytes &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;size
CPU1 data recorded at &lt;span class=&quot;nv&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0x672000
    4096 bytes &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;size
CPU2 data recorded at &lt;span class=&quot;nv&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0x673000
    0 bytes &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;size
CPU3 data recorded at &lt;span class=&quot;nv&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0x673000
    0 bytes &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;size&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Seeing that there are non-zero bytes captured is always a good sign. To view the output you need to issue &lt;em&gt;report&lt;/em&gt; command.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;trace-cmd report

kworker/&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; 85164.256606: &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt;:              process_adv_report
kworker/&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; 85164.256607: &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt;:                 hci_find_irk_by_rpa
kworker/&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; 85164.256608: &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt;:                 has_pending_adv_report
kworker/&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; 85164.256609: &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt;:                 mgmt_device_found
kworker/&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; 85164.256610: &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt;:                    hci_discovery_active
kworker/&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; 85164.256612: &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt;:                    link_to_bdaddr.part.10&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h2 id=&quot;into-the-ebpf&quot;&gt;Into the eBPF&lt;/h2&gt;

&lt;p&gt;At this point I had a bunch of function calls, that I should use to somehow get the useful info about my phone’s Bluetooth state. You can find code of all the functions in kernel source code so in theory it should be matter of time to find the right one to trace. Well, it was pretty significant matter of time, spent on recalling C basics, HCI protocol events and bpftrace, which is what I wanted use in the end.&lt;/p&gt;

&lt;p&gt;So how the trial and error part looked like? I had to first install bpftrace from snap and overcome lockdown (see &lt;a href=&quot;https://github.com/iovisor/bpftrace/issues/853&quot;&gt;this issue&lt;/a&gt;). You need to run all those commands as root too.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;snap &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;bpftrace
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;snap connect bpftrace:system-trace&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I used kprobe, which is the kernel debugging mechanism that can be attached to function execution. Whenever function is entered you can access all its arguments (in bpftrace). So for example let’s say we want to trace &lt;em&gt;hci_cmd_complete_evt&lt;/em&gt;, defined as:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;hci_cmd_complete_evt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hci_dev&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hdev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sk_buff&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;skb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
				 &lt;span class=&quot;n&quot;&gt;u16&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;opcode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;u8&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
				 &lt;span class=&quot;n&quot;&gt;hci_req_complete_t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req_complete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
				 &lt;span class=&quot;n&quot;&gt;hci_req_complete_skb_t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req_complete_skb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;As you can see, there is hdev argument of type hci_dev which turns out to be structure holding a lot of information about the Bluetooth device. Let’s have a look just at the top fields of this struct.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hci_dev&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;list_head&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mutex&lt;/span&gt;	&lt;span class=&quot;n&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt;		&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Knowing all above, I could print the name of local device using below script.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# bt_dev_read.bt&lt;/span&gt;
1 &lt;span class=&quot;c&quot;&gt;#!/snap/bin/bpftrace&lt;/span&gt;
2 
3 &lt;span class=&quot;c&quot;&gt;#include &amp;lt;net/bluetooth/bluetooth.h&amp;gt;&lt;/span&gt;
4 &lt;span class=&quot;c&quot;&gt;#include &amp;lt;net/bluetooth/hci_core.h&amp;gt;&lt;/span&gt;
5 
6 
7 kprobe:hci_cmd_complete_evt
8 &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
9 &lt;span class=&quot;nv&quot;&gt;$dev&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=(&lt;/span&gt; struct hci_dev &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; arg0&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
10 &lt;span class=&quot;nb&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;%s&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;, &lt;span class=&quot;nv&quot;&gt;$dev&lt;/span&gt;-&amp;gt;name&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
11 &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;So yes, bpftrace has its own language similar to C but consisting of only group of functions (see &lt;a href=&quot;https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md#bpftrace-reference-guide&quot;&gt;reference guide&lt;/a&gt;]). It is often used in a form of one-liners, but you can put it into file with shebang as I did.&lt;/p&gt;

&lt;p&gt;Going through the code, we need to first include kernel headers (lines 3-4) which is used to cast arg0 into hci_dev struct (line 9). In line 7, I’m attaching the code to kprobe:hci_cmd_complete_evt, which is eBPF at its beauty - you can execute your own code in kernel as a reaction to kernel event. In this case the reaction is just printing device name (line 10) every time &lt;em&gt;hci_cmd_complete_evt&lt;/em&gt; function is entered. After running it you should see new line with device name every few seconds.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# bt_dev_read.bt&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;./bt_dev_read.bt 
Attaching 1 probe...
hci0
hci0
hci0
hci0&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I had to also make sure that the target function is triggered frequently enough, so I found more usable function called &lt;em&gt;mgmt_device_found&lt;/em&gt; that had all I need: MAC address (arg1) and RSSI (Received Signal Strength Indication - arg5).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;mgmt_device_found&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hci_dev&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hdev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bdaddr_t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bdaddr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;u8&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;link_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
		       &lt;span class=&quot;n&quot;&gt;u8&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;addr_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;u8&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dev_class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s8&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rssi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;u32&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;flags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
		       &lt;span class=&quot;n&quot;&gt;u8&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;eir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;u16&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eir_len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;u8&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;scan_rsp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;u8&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scan_rsp_len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Even though bpftrace support array &lt;em&gt;[]&lt;/em&gt; operator I had problems reading correctly bdaddr struct with its underlying array, so I end up using C trick of incrementing pointer to array directly. You can find more details in below snippet - I divided MAC into to halves and then put it back in printf. I’m also printing rssi after a space in same line.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# bt_rssi.bt&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#!/snap/bin/bpftrace&lt;/span&gt;

kprobe:mgmt_device_found
&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$mac1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;arg1+3&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &amp;amp; 0xffffff&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$mac2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;arg1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &amp;amp; 0xffffff&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;%X%X %d&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;, &lt;span class=&quot;nv&quot;&gt;$mac1&lt;/span&gt;,&lt;span class=&quot;nv&quot;&gt;$mac2&lt;/span&gt;, arg5&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The output from running this code looks as follows.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;./bt_rssi.bt 
Attaching 1 probe...
7AC8715201FD &lt;span class=&quot;nt&quot;&gt;-75&lt;/span&gt;
C2C62D1EC7 &lt;span class=&quot;nt&quot;&gt;-79&lt;/span&gt;
30144A848783 &lt;span class=&quot;nt&quot;&gt;-87&lt;/span&gt;
C2C62D1EC7 &lt;span class=&quot;nt&quot;&gt;-82&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;As a result I have per MAC signal strength that I can later on use to trigger lock and unlock of my Ubuntu system. That is great, but why not find disconnect function that would trigger system suspend whenever I disable Bluetooth on my phone. Fortunately it is there and its called &lt;em&gt;mgmt_device_disconnected&lt;/em&gt; the bpftrace to output disconnected MAC can be found below.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# bt_disconnect.bt&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#!/snap/bin/bpftrace&lt;/span&gt;

kprobe:mgmt_device_disconnected
&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$mac1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;arg1+3&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &amp;amp; 0xffffff&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$mac2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;arg1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &amp;amp; 0xffffff&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;%X%X&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;, &lt;span class=&quot;nv&quot;&gt;$mac1&lt;/span&gt;,&lt;span class=&quot;nv&quot;&gt;$mac2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h2 id=&quot;system-lock-and-suspend-handling&quot;&gt;System lock and suspend handling&lt;/h2&gt;

&lt;p&gt;We have now bt_rssi.bt showing MAC with corresponding signal power and bt_diconnect.bt showing MAC that just disconnected. It is time to shell-script it into actual system actions.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# bt_handler.sh&lt;/span&gt;
1 &lt;span class=&quot;c&quot;&gt;#!/usr/bin/env bash&lt;/span&gt;
2 &lt;span class=&quot;nv&quot;&gt;TARGET_MAC&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt;
3 &lt;span class=&quot;nv&quot;&gt;LOCKFILE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;bt.lock
4 
5 bt_lock&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
6 &lt;span class=&quot;nv&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
7 &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[0]&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$TARGET_MAC&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
8 &lt;span class=&quot;k&quot;&gt;then
&lt;/span&gt;9   &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[1]&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-lt&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-75&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$LOCKFILE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
10   &lt;span class=&quot;k&quot;&gt;then
&lt;/span&gt;11     &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$LOCKFILE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
12     &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;locked&quot;&lt;/span&gt;
13     loginctl lock-sessions
14   &lt;span class=&quot;k&quot;&gt;fi
&lt;/span&gt;15   &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[1]&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-gt&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-65&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$LOCKFILE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
16   &lt;span class=&quot;k&quot;&gt;then
&lt;/span&gt;17     &lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$LOCKFILE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
18     &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;unlocked&quot;&lt;/span&gt;
19     loginctl unlock-sessions
20   &lt;span class=&quot;k&quot;&gt;fi
&lt;/span&gt;21 &lt;span class=&quot;k&quot;&gt;fi
&lt;/span&gt;22 &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
23 
24 &lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;TARGET_MAC
25 &lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;LOCKFILE
26 &lt;span class=&quot;nb&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; bt_lock
27 
28 ./bt_disconnect.bt | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--line-buffered&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$TARGET_MAC&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | xargs &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-n1&lt;/span&gt; bash &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&amp;gt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$LOCKFILE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;; /bin/systemctl suspend&quot;&lt;/span&gt; &amp;amp;  
29 ./bt_rssi.bt | xargs &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-n1&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-I&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;{}&apos;&lt;/span&gt; bash &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;bt_lock &quot;$@&quot;&apos;&lt;/span&gt; _ &lt;span class=&quot;o&quot;&gt;{}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The script takes only one argument - target MAC address (i.e. your phone Bluetooth MAC) and uses LOCKFILE file to mark if system is locked already. &lt;em&gt;bt_lock()&lt;/em&gt; function parses the output of bt_rssi.bt to decide on locking or unlocking the session. Because xargs in line 29 passes one argument in form “&amp;lt;MAC&amp;gt; &amp;lt;RSSI&amp;gt;”, it needs to be broken into two arguments using bash array in line 6.&lt;/p&gt;

&lt;p&gt;Since xargs starts a separate process I had to export all the variables and function in lines 24-26. Last two lines of the script are just actual executions of bpftrace scripts.&lt;/p&gt;

&lt;p&gt;After running the whole script with Bluetooth working on both your pc and phone, you should see system being locked whenever you go away for ~2 meters and unlocked when phone is back near the pc. Turing off the Bluetooth on your phone will result in suspend. The extra feature is that when you turn on the Bluetooth after suspend and wake pc up it becomes unlocked, all of that without typing the password.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Just remember that the script showed in this article was created for educational purposes (just for fun) and it shouldn’t be used in places where you are exposed to anybody that could reach your keyboard. This is mainly because of the fact that your Bluetooth MAC becomes effectively password to your system, and despite of techniques that hides Bluetooth MAC it should be treated as public information. It works ok when using it at home, during pandemic, when the only hackers nearby are 2 and 6 years old and mostly using brute-force keyboard attacks, not Bluetooth MAC spoofing.&lt;/p&gt;

&lt;p&gt;As for eBPF, it is definitely very interesting concept that gains adoption in multiple fields. Vision behind it is far broader than tracing, performance and security cases. eBPF against current service mesh implementations with all its significant overhead is something worth looking at closely.&lt;/p&gt;

&lt;p&gt;You can find all the code I used here in &lt;a href=&quot;https://github.com/devopsspiral/ebpf-bt-unlock&quot;&gt;my git repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
</description>
				<guid isPermaLink="true">https://devopsspiral.com/articles/linux/ebpf-unlock/</guid>
			</item>
		
			<item>
				<title>ChatOps level up with StackStorm</title>
				<link>https://devopsspiral.com/video/chatops-stackstorm/</link>
				<pubDate>Sun, 23 Aug 2020 00:00:00 +0000</pubDate>
				<description>
</description>
				<guid isPermaLink="true">https://devopsspiral.com/video/chatops-stackstorm/</guid>
			</item>
		
			<item>
				<title>Testing with octopus</title>
				<link>https://devopsspiral.com/articles/k8s/testing-with-octopus/</link>
				<pubDate>Wed, 29 Jul 2020 00:00:00 +0000</pubDate>
				<description>&lt;p&gt;While looking for some tooling for testing Kubernetes deployments I’ve bumped into &lt;a href=&quot;https://github.com/kyma-incubator/octopus&quot;&gt;octopus&lt;/a&gt; made as a part of Kyma project. It allows running tests in similar manner as helm test but treating test definitions and executions as kubernetes resources. I thought it is perfect tool for running tests that are encapsulated as containers, i.e. rf-service that I’m working on. In this article I’m describing how I made &lt;a href=&quot;https://github.com/devopsspiral/KubeLibrary&quot;&gt;KubeLibrary&lt;/a&gt;, &lt;a href=&quot;https://github.com/devopsspiral/rf-service/tree/v0.3.1&quot;&gt;rf-service&lt;/a&gt; and octopus work together.&lt;/p&gt;

&lt;div class=&quot;panel radius&quot;&gt;
  &lt;p id=&quot;toc&quot;&gt;&lt;strong&gt;Table of Contents&lt;/strong&gt;&lt;/p&gt;
&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#context&quot; id=&quot;markdown-toc-context&quot;&gt;Context&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#why-octopus-is-such-a-good-fit&quot; id=&quot;markdown-toc-why-octopus-is-such-a-good-fit&quot;&gt;Why octopus is such a good fit?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#essential-changes-to-rf-service&quot; id=&quot;markdown-toc-essential-changes-to-rf-service&quot;&gt;Essential changes to rf-service&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#cli-support&quot; id=&quot;markdown-toc-cli-support&quot;&gt;CLI support&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#execution-by-tag&quot; id=&quot;markdown-toc-execution-by-tag&quot;&gt;Execution by tag&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#dependency-resolution&quot; id=&quot;markdown-toc-dependency-resolution&quot;&gt;Dependency resolution&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#running-tests&quot; id=&quot;markdown-toc-running-tests&quot;&gt;Running tests&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusion&quot; id=&quot;markdown-toc-conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;/div&gt;

&lt;h2 id=&quot;context&quot;&gt;Context&lt;/h2&gt;

&lt;p&gt;This article is a part of series connected with testing on Kubernetes. You can find more info in following articles:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://devopsspiral.com/articles/k8s/robotframework-kubelibrary/&quot;&gt;Robot Framework library for testing Kubernetes&lt;/a&gt; - in this part I’m describing Robot Framework library (Python) that uses Kubernetes client for getting info about your cluster and turning it into actual test suites.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://devopsspiral.com/articles/k8s/robotframework-service/&quot;&gt;Testing on kubernetes - rf-service&lt;/a&gt; - this article describes Python service executed in a form of CronJob that actually runs the tests from KubeLibrary on kubernetes cluster.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h2 id=&quot;why-octopus-is-such-a-good-fit&quot;&gt;Why octopus is such a good fit?&lt;/h2&gt;

&lt;p&gt;Testing kubernetes deployments is for sure something that is not that evolved as other parts of kubernetes world. We assume that things defined in YAML files will be delivered as we expect but does it really differ from regular programming? It becomes even more crucial when the final YAML is templated as in helm case. This was noticed by helm team and implemented as &lt;a href=&quot;https://helm.sh/docs/topics/chart_tests/&quot;&gt;helm test&lt;/a&gt; functionality. The concept of encapsulating all the needed testing tools in container and running them against deployment is neat, the problem is that those tests are not treated as first class kubernetes citizens.&lt;/p&gt;

&lt;p&gt;Kyma-project Octopus took steps towards making this happen, you can find whole motivation in their &lt;a href=&quot;https://kyma-project.io/blog/2020/1/16/integration-testing-in-k8s&quot;&gt;blog post&lt;/a&gt;. Basically, they turned 0/1 helm test approach into handling test as actual resource that can be retried, filtered and executed in parallel. It was used in integration testing context, but could be easily used as recurring health checks.&lt;/p&gt;

&lt;p&gt;Now you only need container with your test suites to execute on demand, which is exactly what rf-service is supposed to provide. Till now it was triggered as CronJob so there was schedule and possible repeatability, implemented changes allows taking advantage of running it within octopus. You can find more info about what was actually added in below section.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h2 id=&quot;essential-changes-to-rf-service&quot;&gt;Essential changes to rf-service&lt;/h2&gt;

&lt;p&gt;In this article I’m only focusing on running rf-service as standalone container, but it can be started as REST API based service for running tests on demand.&lt;/p&gt;

&lt;h3 id=&quot;cli-support&quot;&gt;CLI support&lt;/h3&gt;

&lt;p&gt;Till now rf-service configuration was done by passing .json file with &lt;em&gt;fetcher&lt;/em&gt; - functionality for collecting tests suites and &lt;em&gt;publisher&lt;/em&gt; - functionality for publishing robotframework results. This meant that there was a need to make the container read some file or be mounted with it - by attaching ConfigMap for example. Running it using octopus demanded adding possibility to define all configuration using CLI parameters.&lt;/p&gt;

&lt;p&gt;To avoid maintaining growing list of hardcoded CLI parameters, they are generated dynamically from metadata of fetcher and publisher classes. Meaning that below CLI execution:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;rf-service &lt;span class=&quot;nt&quot;&gt;--LocalFetcher-src&lt;/span&gt; ~/test/source &lt;span class=&quot;nt&quot;&gt;--LocalPublisher-dest&lt;/span&gt; ~/test/results&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;is equivalent with following json configuration file:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;fetcher&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;LocalFetcher&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;src&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;~/test/source&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;publisher&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;LocalPublisher&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;dest&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;~/test/results&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;allowing pretty flexible configuration of rf-service.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h3 id=&quot;execution-by-tag&quot;&gt;Execution by tag&lt;/h3&gt;

&lt;p&gt;Tagging tests in Robot Framework is powerful way of handling test execution and in general categorizing testcases. Since rf-service fetches complete test suites, allowing executing only part of them is a must and has been mirrored from Robot Framework CLI. To include tags pass &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-i &amp;lt;tag&amp;gt;&lt;/code&gt; to exclude tags use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-e &amp;lt;tag&amp;gt;&lt;/code&gt;. Those values are then passed to Robot Framework so you can expect all the behaviors to be exactly the same.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h3 id=&quot;dependency-resolution&quot;&gt;Dependency resolution&lt;/h3&gt;

&lt;p&gt;In a path towards making rf-service generic enough to be executed as a base for different kinds of testcases, support for pip requirements was added. This way if fetcher collects directory containing &lt;em&gt;requirements.txt&lt;/em&gt; file, it will install packages as with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pip install -r requirements.txt&lt;/code&gt;. Just remember first spotted requirements.txt file will be used, so it is best to keep one in top level directory.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h2 id=&quot;running-tests&quot;&gt;Running tests&lt;/h2&gt;

&lt;p&gt;Alright let’s run some tests then. I will be running testcases from &lt;a href=&quot;https://github.com/devopsspiral/KubeLibrary/tree/v0.1.4/testcases&quot;&gt;KubeLibrary/testcases&lt;/a&gt; and using KubeLibrary as a testing library itself.&lt;/p&gt;

&lt;p&gt;As a first step you need to install octopus from the chart provided in its &lt;a href=&quot;https://github.com/kyma-incubator/octopus&quot;&gt;repository&lt;/a&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;git clone https://github.com/kyma-incubator/octopus
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;octopus
helm &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;octopus ./chart/octopus/&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Then, we need to define what and how the tests will be executed. All those things are configured using kubernetes resources (CRD) brought by octopus. In &lt;em&gt;TestDefinition&lt;/em&gt; you define how the test container is executed in similar way kubernetes deployment is defined. In my case, test definition is defined as follows:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# test-definition.yaml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;testing.kyma-project.io/v1alpha1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;TestDefinition&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;kube-tests&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test-example&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;serviceAccountName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;octopus-sa&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;containers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;mwcislo/rf-service:0.3.0&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;rf-service&quot;&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--ZipFetcher-url&quot;&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;https://github.com/devopsspiral/KubeLibrary/archive/v0.1.4.zip&quot;&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--ZipFetcher-path&quot;&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;KubeLibrary-0.1.4/testcases&quot;&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--LocalPublisher-dest&quot;&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;somecontext&quot;&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-i&quot;&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;octopus&quot;&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KLIB_POD_PATTERN&lt;/span&gt;
                    &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;octopus.*&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KLIB_POD_NAMESPACE&lt;/span&gt;
                    &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;default&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KLIB_POD_LABELS&lt;/span&gt;
                    &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;octopus&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}&quot;&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KLIB_ENV_VARS&lt;/span&gt;
                    &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;SECRET_NAME&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;webhook-server-secret&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}&quot;&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KLIB_RESOURCE_REQUESTS_CPU&lt;/span&gt;
                    &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;100m&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KLIB_RESOURCE_REQUESTS_MEMORY&lt;/span&gt;
                    &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;20Mi&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KLIB_RESOURCE_LIMITS_CPU&lt;/span&gt;
                    &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;100m&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KLIB_RESOURCE_LIMITS_MEMORY&lt;/span&gt;
                    &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;30Mi&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Shortly speaking I’m telling rf-service to take tests from KubeLibrary repository (branch master) on path testcases/ and publish results locally in directory &lt;em&gt;somecontext&lt;/em&gt; - the actual path doesn’t really matter in this case. The execution should only include tests with tag &lt;em&gt;octopus&lt;/em&gt;. All the environment variables are just parameterized test variables, it is attempt to make the tests more generic and possibly use same tests with different test targets, i.e. different services.&lt;/p&gt;

&lt;p&gt;You might noticed &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;serviceAccountName: octopus-sa&lt;/code&gt; part, this is needed to run the test container in privileged mode so that it can talk to different parts of kubernetes API and depends on what you really need to test. Below I’m showing the resources that needs to be applied to make it cluster-admin privileged.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# serviceaccount.yaml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ServiceAccount&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;octopus-sa&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ClusterRoleBinding&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;octopus-admin-binding&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;default&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;subjects&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ServiceAccount&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;octopus-sa&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;default&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;roleRef&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ClusterRole&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cluster-admin&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;apiGroup&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;rbac.authorization.k8s.io&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The second part that needs to be applied is &lt;em&gt;ClusterTestSuite&lt;/em&gt;, which represents how the test should be actually executed. Tests are matched by label, which again allows convenient way of grouping tests. Other parameters are &lt;strong&gt;count&lt;/strong&gt; - how many times test should be repeated, &lt;strong&gt;maxRetries&lt;/strong&gt; - how many times can be repeated if failed and &lt;strong&gt;concurrency&lt;/strong&gt; - how many tests can run in parallel, in our case we only have one test so any changes here takes no effect, but when you are running a lot of tests (TestsDefinitions) it can save a lot of time.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# cluster-test-suite.yaml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;testing.kyma-project.io/v1alpha1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ClusterTestSuite&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;controller-tools.k8s.io&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;1.0&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;testsuite-selected-by-labels&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;maxRetries&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;concurrency&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;selectors&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;matchLabelExpressions&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;component=kube-tests&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;To run the tests just apply all the files using kubectl, starting with serviceaccount.yaml. After couple of seconds you should see new pod being created and inside of it, there will be rf-service running KubeLibrary tests from within the cluster. You should see all the results in container logs, to view them just use regular logs lookup &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubectl logs oct-tp-testsuite-selected-by-labels-test-example-0&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;==============================================================================&lt;/span&gt;
A6D70814-B63C-49Ba-B82F-8778B7D86De9                                          
&lt;span class=&quot;o&quot;&gt;==============================================================================&lt;/span&gt;
A6D70814-B63C-49Ba-B82F-8778B7D86De9.KubeLibrary-master                       
&lt;span class=&quot;o&quot;&gt;==============================================================================&lt;/span&gt;
A6D70814-B63C-49Ba-B82F-8778B7D86De9.KubeLibrary-master.Testcases             
&lt;span class=&quot;o&quot;&gt;==============================================================================&lt;/span&gt;
A6D70814-B63C-49Ba-B82F-8778B7D86De9.KubeLibrary-master.Testcases.Pod         
&lt;span class=&quot;o&quot;&gt;==============================================================================&lt;/span&gt;
A6D70814-B63C-49Ba-B82F-8778B7D86De9.KubeLibrary-master.Testcases.Pod.Pod     
&lt;span class=&quot;o&quot;&gt;==============================================================================&lt;/span&gt;
Pod images has correct version                                        | PASS |
&lt;span class=&quot;nt&quot;&gt;------------------------------------------------------------------------------&lt;/span&gt;
Pod has enough replicas                                               | PASS |
&lt;span class=&quot;nt&quot;&gt;------------------------------------------------------------------------------&lt;/span&gt;
Pod has not been restarted                                            | PASS |
&lt;span class=&quot;nt&quot;&gt;------------------------------------------------------------------------------&lt;/span&gt;
Pod have correct labels                                               | PASS |
&lt;span class=&quot;nt&quot;&gt;------------------------------------------------------------------------------&lt;/span&gt;
Pod has correct limits/requests                                       | PASS |
&lt;span class=&quot;nt&quot;&gt;------------------------------------------------------------------------------&lt;/span&gt;
Pod has correct &lt;span class=&quot;nb&quot;&gt;env &lt;/span&gt;variables                                         | PASS |
&lt;span class=&quot;nt&quot;&gt;------------------------------------------------------------------------------&lt;/span&gt;
A6D70814-B63C-49Ba-B82F-8778B7D86De9.KubeLibrary-master.Testcases.... | PASS |
6 critical tests, 6 passed, 0 failed
6 tests total, 6 passed, 0 failed
&lt;span class=&quot;o&quot;&gt;==============================================================================&lt;/span&gt;
A6D70814-B63C-49Ba-B82F-8778B7D86De9.KubeLibrary-master.Testcases.Pod | PASS |
6 critical tests, 6 passed, 0 failed
6 tests total, 6 passed, 0 failed
&lt;span class=&quot;o&quot;&gt;==============================================================================&lt;/span&gt;
A6D70814-B63C-49Ba-B82F-8778B7D86De9.KubeLibrary-master.Testcases     | PASS |
6 critical tests, 6 passed, 0 failed
6 tests total, 6 passed, 0 failed
&lt;span class=&quot;o&quot;&gt;==============================================================================&lt;/span&gt;
A6D70814-B63C-49Ba-B82F-8778B7D86De9.KubeLibrary-master               | PASS |
6 critical tests, 6 passed, 0 failed
6 tests total, 6 passed, 0 failed
&lt;span class=&quot;o&quot;&gt;==============================================================================&lt;/span&gt;
A6D70814-B63C-49Ba-B82F-8778B7D86De9                                  | PASS |
6 critical tests, 6 passed, 0 failed
6 tests total, 6 passed, 0 failed
&lt;span class=&quot;o&quot;&gt;==============================================================================&lt;/span&gt;
Output:  /output.xml&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Test execution status is kept in ClusterTestSuite resource, you can use it to view resulting status, start time, completion time and others. This allows getting overview of multiple suites execution and keeping its records.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$kubectl&lt;/span&gt; get ClusterTestSuite
NAME                           AGE
testsuite-selected-by-labels   18s

&lt;span class=&quot;nv&quot;&gt;$kubectl&lt;/span&gt; describe ClusterTestSuite testsuite-selected-by-labels
Name:         testsuite-selected-by-labels
Namespace:    
Labels:       controller-tools.k8s.io&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1.0
Annotations:  API Version:  testing.kyma-project.io/v1alpha1
Kind:         ClusterTestSuite
Metadata:
  Creation Timestamp:  2020-07-26T13:21:14Z
  Generation:          1
  Resource Version:    684
  Self Link:           /apis/testing.kyma-project.io/v1alpha1/clustertestsuites/testsuite-selected-by-labels
  UID:                 f2f11a7d-8927-47cb-a125-0f107018194c
Spec:
  Concurrency:  1
  Count:        1
  Max Retries:  1
  Selectors:
    Match Label Expressions:
      &lt;span class=&quot;nv&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;kube-tests
Status:
  Completion Time:  2020-07-26T13:22:16Z
  Conditions:
    Status:  False
    Type:    Running
    Status:  True
    Type:    Succeeded
  Results:
    Executions:
      Completion Time:  2020-07-26T13:22:16Z
      Id:               oct-tp-testsuite-selected-by-labels-test-example-0
      Pod Phase:        Succeeded
      Start Time:       2020-07-26T13:21:15Z
    Name:               test-example
    Namespace:          default
    Status:             Succeeded
  Start Time:           2020-07-26T13:21:14Z
Events:                 &amp;lt;none&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;In my opinion octopus is very interesting project and might play important role whenever kubernetes is used, by filling testing gap. As someone said YAML became k8s programming language and as every programming language needs some holistic approach to testing. Observing what octopus can and what are the proposals seen in the project &lt;a href=&quot;https://github.com/kyma-incubator/octopus/issues&quot;&gt;issues&lt;/a&gt; seems it can be good move towards it.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
</description>
				<guid isPermaLink="true">https://devopsspiral.com/articles/k8s/testing-with-octopus/</guid>
			</item>
		
			<item>
				<title>Introduction to ChatOps. Errbot + Slack integration.</title>
				<link>https://devopsspiral.com/video/chatops-intro/</link>
				<pubDate>Tue, 12 May 2020 00:00:00 +0000</pubDate>
				<description>
</description>
				<guid isPermaLink="true">https://devopsspiral.com/video/chatops-intro/</guid>
			</item>
		
			<item>
				<title>Intro to Vue.js. Testing on kubernetes - rf-service frontend</title>
				<link>https://devopsspiral.com/articles/k8s/robotframework-service-fe/</link>
				<pubDate>Fri, 10 Apr 2020 00:00:00 +0000</pubDate>
				<description>&lt;p&gt;Vue.js is getting more and more popularity, it is still behind React.js and Angular but catching up. It is known for its clarity and flexibility, that is why it makes a perfect opportunity to learn for frontend noobs such as myself.&lt;/p&gt;

&lt;div class=&quot;panel radius&quot;&gt;
  &lt;p id=&quot;toc&quot;&gt;&lt;strong&gt;Table of Contents&lt;/strong&gt;&lt;/p&gt;
&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#context&quot; id=&quot;markdown-toc-context&quot;&gt;Context&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#why-vuejs-&quot; id=&quot;markdown-toc-why-vuejs-&quot;&gt;Why Vue.js ?&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#installation-and-first-project&quot; id=&quot;markdown-toc-installation-and-first-project&quot;&gt;Installation and first project&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#components&quot; id=&quot;markdown-toc-components&quot;&gt;Components&lt;/a&gt;        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#rendering&quot; id=&quot;markdown-toc-rendering&quot;&gt;Rendering&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#runnervue-and-recursive-components&quot; id=&quot;markdown-toc-runnervue-and-recursive-components&quot;&gt;Runner.vue and recursive components&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#routing&quot; id=&quot;markdown-toc-routing&quot;&gt;Routing&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#dockerizing&quot; id=&quot;markdown-toc-dockerizing&quot;&gt;Dockerizing&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#changes-in-rf-service&quot; id=&quot;markdown-toc-changes-in-rf-service&quot;&gt;Changes in rf-service&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#api&quot; id=&quot;markdown-toc-api&quot;&gt;API&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#changes-in-helm&quot; id=&quot;markdown-toc-changes-in-helm&quot;&gt;Changes in helm&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#testing&quot; id=&quot;markdown-toc-testing&quot;&gt;Testing&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusion&quot; id=&quot;markdown-toc-conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;/div&gt;

&lt;h2 id=&quot;context&quot;&gt;Context&lt;/h2&gt;

&lt;p&gt;This article is a part of series connected with testing on Kubernetes. It is not mandatory to go through previous articles, but it gives a better understanding of what I’m actually trying to achieve here. The Vue.js intro part is written independently so that with below short description of each of the previous parts you should be fine with going through it.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://devopsspiral.com/articles/k8s/robotframework-kubelibrary/&quot;&gt;Robot Framework library for testing Kubernetes&lt;/a&gt; - in this part I’m describing Robot Framework library (Python) that uses Kubernetes client for getting info about your cluster and turning it into actual test suites.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://devopsspiral.com/articles/k8s/robotframework-service/&quot;&gt;Testing on kubernetes - rf-service&lt;/a&gt; - this article describes Python service executed in a form of CronJob that actually runs the tests from KubeLibrary on kubernetes cluster.&lt;/p&gt;

&lt;p&gt;The article your reading is a next step, where rf-service gets api and frontend so that tests can be executed on demand and results can be viewed in single web UI.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h2 id=&quot;why-vuejs-&quot;&gt;Why Vue.js ?&lt;/h2&gt;

&lt;p&gt;Instead of repeating same story of what Vue.js is, I would rather focus why it seemed perfect for me - knowing only basics of HTML, js and CSS. Vue.js is said to leverage best parts from React and Angular, making it also more lightweight and easy to learn. This was most important for me so that I can learn quickly and also not spend time on something that is far from the main players. You can find more detailed comparison created by Vue.js core team - &lt;a href=&quot;https://vuejs.org/v2/guide/comparison.html&quot;&gt;Comparison with Other Frameworks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Vue.js is also in upward trend which makes it perfect competence investment. On top of that you can expect many other people facing similar problems while learning it so access to information and learning should not be a problem. More on that in &lt;a href=&quot;https://medium.com/@inverita/why-is-vue-js-a-front-end-trend-of-2020-4081e8c799aa&quot;&gt;Why Is Vue.js a Front-end Trend of 2020?&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The third point that really caught my attention is that you can write mobile apps using Vue Native. I didn’t tried it yet, but possibility to implement some cool projects on your mobile sounds very tempting.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h3 id=&quot;installation-and-first-project&quot;&gt;Installation and first project&lt;/h3&gt;
&lt;p&gt;The recommended method for installation is through &lt;em&gt;npm&lt;/em&gt;. If you don’t have it yet, this is the prerequisite for further steps. You can install Vue with below command:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;npm &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;vue&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Installing vue-cli makes a lot of things easier, so I used it also. You can add it with:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;npm &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; @vue/cli&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You can create your first project by executing:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;vue create &amp;lt;project-name&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This will create everything you need to see Vue.js in action including structure and example project that can be base for you changes as in my case. To see the UI just go to project folder and start the development server with:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;npm run serve&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You should see Hello world app on &lt;em&gt;http://localhost:8080/&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h3 id=&quot;components&quot;&gt;Components&lt;/h3&gt;

&lt;p&gt;Simplifying things a lot we can say that Vue.js is templating HTMLs documents using JavaScript. Because this is exactly what browser engines works on and understands. Having HTML document, browser can build DOM (Document Object Model) which is the logical representation of HTML text and this is what is then rendered in front of user’s eyes. Modern frameworks uses Virtual DOM which is the higher level abstraction that allows more efficient and manageable way of providing content representation.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h4 id=&quot;rendering&quot;&gt;Rendering&lt;/h4&gt;

&lt;p&gt;Knowing above let’s just track how actually Vue.js code is being placed in final HTML document. Before that, we can already switch to the actual code of rf-service frontend. Just clone the repo from &lt;a href=&quot;https://github.com/devopsspiral/rf-service-fe&quot;&gt;devopsspiral/rf-service-fe&lt;/a&gt;. Again it is vue project created with vue cli.&lt;/p&gt;

&lt;p&gt;We will follow top-down path. If you go to &lt;em&gt;public/index.html&lt;/em&gt;, you will see how the Vue.js app is mounted.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;# public/index.html
&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;app&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This is the place where &lt;em&gt;src/App.vue&lt;/em&gt; is referenced. In App.vue there is &lt;em&gt;template&lt;/em&gt; and &lt;em&gt;style&lt;/em&gt; part, so you just literally create parts of your app as self-contained source of HTML and CSS. This looks really simple and is easy to manage.&lt;/p&gt;

&lt;div class=&quot;alert-box text radius &quot;&gt;&lt;p&gt;Creating .vue files with template, style and js code is not the only way for templating used in Vue. At some point you can find that it is not enough for all the cases, you can find more info &lt;a href=&quot;https://vuejsdevelopers.com/2017/03/24/vue-js-component-templates/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;# src/App.vue
&lt;span class=&quot;nt&quot;&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;app&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;nav&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;router-link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;to=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Runner&lt;span class=&quot;nt&quot;&gt;&amp;lt;/router-link&amp;gt;&lt;/span&gt; |
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;router-link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;to=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/results&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Results&lt;span class=&quot;nt&quot;&gt;&amp;lt;/router-link&amp;gt;&lt;/span&gt;  |
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;router-link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;to=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/config&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Configure&lt;span class=&quot;nt&quot;&gt;&amp;lt;/router-link&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;router-view/&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I will explain routers later on, for now we can just assume that &lt;em&gt;&amp;lt;router-link&amp;gt;&lt;/em&gt; is just a link to other parts of our application. Those can be found in &lt;em&gt;src/views&lt;/em&gt; and &lt;em&gt;src/components&lt;/em&gt;. There is not much difference of how you create views and components, they are kept separate just for clarity. They serve different functions though. Views are used to present parts of your app to the user, while components could be some internal machinery of your app referenced in views.&lt;/p&gt;

&lt;p&gt;In my case Runner.vue, Result.vue and Config.vue are kept as views in &lt;em&gt;src/views&lt;/em&gt; directory. If you look inside those files, you will see that apart from template and style, we have now extra vue/js logic that makes the whole thing dynamic.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h4 id=&quot;runnervue-and-recursive-components&quot;&gt;Runner.vue and recursive components&lt;/h4&gt;

&lt;p&gt;I will just focus on Runner.vue as other views are pretty similar. The template part is simple, we have two components: button and test-list. The first one is just regular HTML button but with vue attribute &lt;em&gt;v-on:click&lt;/em&gt;, that is pointing into function in &lt;em&gt;script&lt;/em&gt; part in &lt;em&gt;method&lt;/em&gt; section. This is how you define action from JS to be linked with HTML object. Just to better understand what is done there I’m performing a POST http call to rf-service api using Axios.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;# src/views/Runner.vue
&lt;span class=&quot;nt&quot;&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;v-on:click=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;runTests&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Run All&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;tests&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;test-list&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;:children=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;tests&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;:indentation=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;0&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/test-list&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt; 
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The second object in template is test-list which is as component in &lt;em&gt;src/components/TestList.vue&lt;/em&gt;. As you can see Vue components are not only modules in Vue.js code, they become actual HTML tags that can be used in templates. Test-list is a bit more interesting because it implements test tree structure using recursion. If you look into its definition it uses recursive call in template section.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;# src/components/TestList.vue
&lt;span class=&quot;nt&quot;&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;:style=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;indent&quot;&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;click=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;toggleChildren&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;v-if=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;showChildren&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- Recursive call --&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;test-list&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;v-for=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;child in children&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;:children=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;child.children&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;:name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;child.name&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;:key=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;child.name&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;:indentation=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;indentation + 1&quot;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;/test-list&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt; 
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Other than that, there is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@click&lt;/code&gt; which is shorthand for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v-on:click&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:style&lt;/code&gt; which is shorthand for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v-bind:style&lt;/code&gt; - this is probably the most used vue directive. It binds the value of the parameter with variable or function in JS and this is where whole reactive magic happens. You don’t need to care about updating anything, whatever changes in JS is reflected in HTML.&lt;/p&gt;

&lt;p&gt;You can also find &lt;em&gt;v-if&lt;/em&gt; and &lt;em&gt;v-for&lt;/em&gt; directives that basically works similar to what can be found in other templating engines (like jinja) and represents conditional rendering and loops. In this case v-if will conditionally render &amp;lt;div&amp;gt;, which is by the way boundary of v-if - you don’t use anything similar to &lt;em&gt;endif&lt;/em&gt; for example. On the other side v-for will multiply test-list tags for each child in children.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;# src/components/TestList.vue
&lt;span class=&quot;nt&quot;&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;test-list&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;indentation&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;tests&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;showChildren&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;computed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;indent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`translate(&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;indentation&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;px)`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;toggleChildren&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;showChildren&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;showChildren&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Going to the script section, there are couple of distinct parts there. &lt;em&gt;name&lt;/em&gt; and &lt;em&gt;props&lt;/em&gt; is defining test-list representation as (HTML) component with parameters. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data()&lt;/code&gt; is the place for all variables available across whole file. Just remember to add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this.&lt;/code&gt; if you are referencing this variable inside script section. And lastly, we have &lt;em&gt;computed&lt;/em&gt; and &lt;em&gt;methods&lt;/em&gt; section. You may wander what is the difference if all in all they are both functions. The difference is in how they are used. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;indent()&lt;/code&gt; is actually used as value not action as in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toggleChildren()&lt;/code&gt; case. This is why Vue needs to take some extra care of this value in a sense of reactivity i.e. compute the new value whenever &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this.indentation&lt;/code&gt; changes.&lt;/p&gt;

&lt;p&gt;In Runner.vue there is another interesting section called &lt;em&gt;created&lt;/em&gt; which is executed on page loading. In this case, it is another call to api to fetch some parameters metadata.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h3 id=&quot;routing&quot;&gt;Routing&lt;/h3&gt;
&lt;p&gt;We did a little round tour of Vue elements and we’re back to the point where we would like to put things together. This is where routers come into play. As in other programming languages routers directs the flow into classes or methods, in Vue.js those will be components. If you have your app already created without router, don’t worry you can always add it using vue cli.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;vue add router&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This will create necessary files in your project and you will be ready to use it. The actual routing paths are kept in &lt;em&gt;src/router/index.js&lt;/em&gt; in routes variable. This is where you link paths with components.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;err&quot;&gt;#&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;router&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;js&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;routes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Runner&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Runner&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;/config&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Config&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Config&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;/results&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Results&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Results&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Having the paths you can now link to them as it was done in App.vue.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;# src/App.vue
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;router-link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;to=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Runner&lt;span class=&quot;nt&quot;&gt;&amp;lt;/router-link&amp;gt;&lt;/span&gt; |
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;router-link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;to=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/results&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Results&lt;span class=&quot;nt&quot;&gt;&amp;lt;/router-link&amp;gt;&lt;/span&gt;  |
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;router-link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;to=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/config&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Configure&lt;span class=&quot;nt&quot;&gt;&amp;lt;/router-link&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h3 id=&quot;dockerizing&quot;&gt;Dockerizing&lt;/h3&gt;
&lt;p&gt;All right, we have our app running well in development, what if we would like to actually share it with others as a dockerized service? This couldn’t be simpler, just use Dockerfile in the repo (taken from &lt;a href=&quot;https://vuejs.org/v2/cookbook/dockerize-vuejs-app.html&quot;&gt;https://vuejs.org/v2/cookbook/dockerize-vuejs-app.html&lt;/a&gt;). It doesn’t have any app specific parts so it can be used as a base for whatever app you are building.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# Dockerfile&lt;/span&gt;
FROM node:lts-alpine

&lt;span class=&quot;c&quot;&gt;# install simple http server for serving static content&lt;/span&gt;
RUN npm &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; http-server

&lt;span class=&quot;c&quot;&gt;# make the &apos;app&apos; folder the current working directory&lt;/span&gt;
WORKDIR /app

&lt;span class=&quot;c&quot;&gt;# copy both &apos;package.json&apos; and &apos;package-lock.json&apos; (if available)&lt;/span&gt;
COPY package&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.json ./

&lt;span class=&quot;c&quot;&gt;# install project dependencies&lt;/span&gt;
RUN npm &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# copy project files and folders to the current working directory (i.e. &apos;app&apos; folder)&lt;/span&gt;
COPY &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# build app for production with minification&lt;/span&gt;
RUN npm run build

EXPOSE 8080
CMD &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;http-server&quot;&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;dist&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h2 id=&quot;changes-in-rf-service&quot;&gt;Changes in rf-service&lt;/h2&gt;

&lt;p&gt;Getting back to broader context of this article, &lt;a href=&quot;https://github.com/devopsspiral/rf-service&quot;&gt;rf-service&lt;/a&gt; got api and frontend. It is still possible to run tests from cron job, but the results can be viewed in Results tab in rf-service-fe. Switch between modes is done by passing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.Values.config&lt;/code&gt; helm parameter with configuration of fetcher and publisher as before. By default it is defined as empty string and all the configuration is done using Vue.js frontend.&lt;/p&gt;

&lt;p&gt;As a result we have now 3 containers running: frontend with Vue.js app, rf-service and its api and Caddy serving as a store for Robot Framework results.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h3 id=&quot;api&quot;&gt;API&lt;/h3&gt;

&lt;p&gt;rf-service has now api with following endpoints&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Path&lt;/th&gt;
      &lt;th&gt;Method&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;/api/publishers&lt;/td&gt;
      &lt;td&gt;GET&lt;/td&gt;
      &lt;td&gt;list of available Publishers&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;/api/publishers_conf&lt;/td&gt;
      &lt;td&gt;POST, GET&lt;/td&gt;
      &lt;td&gt;configure Publisher&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;/api/fetchers&lt;/td&gt;
      &lt;td&gt;GET&lt;/td&gt;
      &lt;td&gt;list of available Fetchers&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;/api/fetchers_conf&lt;/td&gt;
      &lt;td&gt;POST, GET&lt;/td&gt;
      &lt;td&gt;configure Fetcher&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;/api/tests&lt;/td&gt;
      &lt;td&gt;GET&lt;/td&gt;
      &lt;td&gt;view loaded tests&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;/api/run&lt;/td&gt;
      &lt;td&gt;POST&lt;/td&gt;
      &lt;td&gt;run all the tests&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;All the paths have explicit /api/ prefix for simplicity, this could be replaced by Flask blueprints or simply by mounting the app on specific path. Api is also missing swagger docs, which might be covered in future.&lt;/p&gt;

&lt;p&gt;The app in final container is served by gevent which allowed to expose api without extra non-python dependencies and allowed easy implementation of backward compatibility.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h3 id=&quot;changes-in-helm&quot;&gt;Changes in helm&lt;/h3&gt;

&lt;p&gt;As a result of having 3 containers in place. I decided to put them into one pod, this resulted in multiple ports in one service&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;na&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Service&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;-rf-service&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; 
  &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; 
      &lt;span class=&quot;na&quot;&gt;targetPort&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;http&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;TCP&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;http&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;8090&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;targetPort&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;caddy&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;TCP&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;caddy&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;5000&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;targetPort&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;api&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;TCP&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;api&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;and ingress. Expose over ingress became mandatory to allows easy handling on frontend side with paths /caddy/ and /api/. This is exactly why rf-service api needs to have /api/ prefix. Caddy /caddy/ path is handled by using rewrite (see &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.Values.caddy.setup&lt;/code&gt; and snippet below).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;  &lt;span class=&quot;na&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; 
      &lt;span class=&quot;na&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/&quot;&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;serviceName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; 
              &lt;span class=&quot;na&quot;&gt;servicePort&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; 
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/caddy/&quot;&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;serviceName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; 
              &lt;span class=&quot;na&quot;&gt;servicePort&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;8090&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/api/&quot;&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;serviceName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; 
              &lt;span class=&quot;na&quot;&gt;servicePort&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;5000&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;    &lt;span class=&quot;c1&quot;&gt;# Bind address&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;:8090&lt;/span&gt;

    &lt;span class=&quot;s&quot;&gt;tls off&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;log stdout&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;errors stderr&lt;/span&gt;

    &lt;span class=&quot;s&quot;&gt;# After this line, all other paths are relative to root.&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;root /tmp/store&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;browse /&lt;/span&gt;

    &lt;span class=&quot;s&quot;&gt;rewrite /caddy/ {&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;to /{file} /&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;upload /uploads {&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;to &quot;/tmp/store&quot;&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h2 id=&quot;testing&quot;&gt;Testing&lt;/h2&gt;

&lt;p&gt;You can test the whole setup by first installing the chart on your k8s cluster (k3d in my case):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;helm &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;rf-service chart/rf-service&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;and then setting the example config:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;fetcher&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ZipFetcher&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://github.com/devopsspiral/rf-service/archive/master.zip&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;rf-service-master/test/resources/testcases&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;publisher&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;CaddyPublisher&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;http://localhost:8090/uploads&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I’m using localhost:8090 only because in this setup all the containers are in same pod and accessible via localhost on different ports. CaddyPublisher address is used by rf-srvice not external client, like browser.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;The created frontend doesn’t have styling and still needs some more work, but building it with Vue.js was real fun. After getting the whole idea of components it was quite easy to add new stuff there. Solutions to most of my initial problems could be found in great Vue.js materials or on forums. Vue.js is really great tool for creating both simple and complex apps and it is definitely a lot of learning ahead of me in this matter.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;#toc&quot;&gt;Back to table of contents&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
</description>
				<guid isPermaLink="true">https://devopsspiral.com/articles/k8s/robotframework-service-fe/</guid>
			</item>
		
	</channel>
</rss>
