<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Yaraslau Kurmyza</title>
    <description>Frontend, Backend, Full-stack, DevOps
</description>
    <link>http://yaraslav.com//</link>
    <atom:link href="http://yaraslav.com//feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Sun, 22 Dec 2024 17:42:35 +0000</pubDate>
    <lastBuildDate>Sun, 22 Dec 2024 17:42:35 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>Firefox-CI 502 errors post mortem</title>
        <description>&lt;p&gt;Below is a multi-month investigation of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;502&lt;/code&gt; GLB errors. Scroll down to &lt;a href=&quot;#summary&quot;&gt;summary&lt;/a&gt; for the tl;dr&lt;/p&gt;

&lt;h1 id=&quot;firefox-ci-502s-post-mortem&quot;&gt;Firefox-CI 502s post mortem&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://firefox-ci-tc.services.mozilla.com/&quot;&gt;Firefox-CI&lt;/a&gt; (a &lt;a href=&quot;https://github.com/taskcluster/taskcluster&quot;&gt;Taskcluster&lt;/a&gt; deployment) is a distributed system that orchestrates millions of tasks daily, making sure they are being scheduled, executed and resolved. It was built in such a way that any operation could be retried in case of a server failure.&lt;/p&gt;

&lt;p&gt;However, if you have large amounts of errors, it might make the system much slower, increase execution time of tasks and introduce unnecessary delays and disruptions.&lt;/p&gt;

&lt;p&gt;Firefox-CI runs on Google Cloud Platform in Kubernetes and uses Google Load Balancer.&lt;/p&gt;

&lt;h2 id=&quot;timeline&quot;&gt;Timeline&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/img/moz/glb-02.png&quot; alt=&quot;502 errors&quot; title=&quot;502 errors&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After noticing a big amount of &lt;strong&gt;5xx&lt;/strong&gt; daily errors and were able to find the root causes and reduce those from &lt;strong&gt;40k-50k&lt;/strong&gt;/day to zero.&lt;/p&gt;

&lt;p&gt;Almost all of those &lt;strong&gt;502&lt;/strong&gt; errors were sent with a timeout (30s - 120s), which means that clients had to wait around &lt;strong&gt;75s&lt;/strong&gt; on average before retrying failed calls.&lt;/p&gt;

&lt;p&gt;That means that we could save daily on average &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;45.000 * 75s = 3.375.000s&lt;/code&gt;  (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~930 hours&lt;/code&gt;).&lt;/p&gt;

&lt;h2 id=&quot;investigation&quot;&gt;Investigation&lt;/h2&gt;

&lt;p&gt;While identifying an issue with workers, which would shut themselves down while running a task, I noticed that a few API calls returned &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;502&lt;/code&gt; errors around the same time.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/taskcluster/taskcluster/issues/6681&quot;&gt;#6681&lt;/a&gt; &lt;a href=&quot;https://github.com/taskcluster/taskcluster/issues/6682&quot;&gt;#6682&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One particular issue with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;502&lt;/code&gt; is that it was happening on the Load Balancer and client side. But on the services side, logs suggested that the call executed successfully and returned some payload.&lt;/p&gt;

&lt;p&gt;Flow is:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Worker calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;queue.claimWork()&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Queue service handles the request and sends back response with the payload&lt;/li&gt;
  &lt;li&gt;Response is lost at transmission somewhere between the service and Load Balancer&lt;/li&gt;
  &lt;li&gt;Worker gets a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;502&lt;/code&gt; error and keeps retrying the call.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; That means that the system thinks that worker received a response, but in reality, it was lost somewhere in the network stack.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Client libraries can handle server-side errors, but the problem with those is that it can take a random amount of time to see that error, usually between 30s-120s.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Schematically, network stack in cloud infrastructure consists of:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;API server (node.js application) running on a kubernetes &lt;strong&gt;POD&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Kubernetes &lt;strong&gt;service&lt;/strong&gt; that exposes this particular pod&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Ingress&lt;/strong&gt; service that proxies all incoming calls to the underlying services&lt;/li&gt;
  &lt;li&gt;Load Balancer’s &lt;strong&gt;backend&lt;/strong&gt; that is running on instance groups (k8s nodes)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Google Load Balancer&lt;/strong&gt; that accepts HTTP requests from the world and passes down to services&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This leads to an assumption that calls are “getting lost” somewhere between 1-5 elements of this chain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question&lt;/strong&gt;: where exactly? (Spoiler: everywhere)&lt;/p&gt;

&lt;p&gt;Checking logs showed that those &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;502&lt;/code&gt; errors happen frequently and in extremely large quantities!&lt;/p&gt;

&lt;p&gt;Somewhere around &lt;strong&gt;40.000 - 50.000&lt;/strong&gt; errors a day!&lt;/p&gt;

&lt;p&gt;Analysis of existing errors revealed some patterns:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Most of the &lt;strong&gt;5xx&lt;/strong&gt; errors are &lt;strong&gt;502s&lt;/strong&gt; (&amp;gt;95%)&lt;/li&gt;
  &lt;li&gt;502 errors have different types of errors:
    &lt;ol&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;response_sent_by_backend&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;backend_timeout&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;failed_to_connect_to_backend&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;backend_connection_closed_before_data_sent_to_client&lt;/code&gt;&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;Errors were usually seen in spikes, some of them happened daily around the same time&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;suspect-1---keepalive-timeouts&quot;&gt;Suspect #1 - keepalive timeouts&lt;/h2&gt;

&lt;p&gt;Based on types of errors, one in particular stood out - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;backend_connection_closed_before_data_sent_to_client&lt;/code&gt; which contributed to more than 85% of failures.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://cloud.google.com/load-balancing/docs/https/troubleshooting-ext-https-lbs&quot;&gt;GCP’s troubleshooting guide&lt;/a&gt; hinted that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;keepalive timeout&lt;/code&gt; might be out of sync between GLB, ingress and services.&lt;/p&gt;

&lt;p&gt;GLB has fixed keepalive timeout of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;600s&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Nginx’s (ingress) keepalive timeout was not set, as well as service’s.&lt;/p&gt;

&lt;p&gt;That means that GLB can keep connections open and send requests after a connection was being closed by ingress or service.&lt;/p&gt;

&lt;p&gt;Patch was applied to set bigger keepalive timeouts for the nginx ingress service.&lt;/p&gt;

&lt;p&gt;Similar change was applied on the services side: &lt;a href=&quot;https://github.com/taskcluster/taskcluster/pull/6692&quot;&gt;#6692&lt;/a&gt; to make server keep connection to ingress open longer. &lt;a href=&quot;https://github.com/mozilla-services/cloudops-infra/pull/5267&quot;&gt;#5267&lt;/a&gt; was added to set some numbers for the deployment.&lt;/p&gt;

&lt;p&gt;Keepalive timeout tweaks were helping - number of errors dropped from &lt;strong&gt;40-50k&lt;/strong&gt; a day to &lt;strong&gt;4-5k&lt;/strong&gt; a day&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/moz/glb-02.png&quot; alt=&quot;502 GLB errors&quot; title=&quot;502 GLB errors&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Backend_connection_closed_before_data_sent_to_client&lt;/code&gt; errors were mostly gone, and new target was to find out what causes remaining timeouts.&lt;/p&gt;

&lt;h2 id=&quot;suspect-2---pod-autoscaling&quot;&gt;Suspect #2 - pod autoscaling&lt;/h2&gt;

&lt;p&gt;Once the majority of errors disappeared, it became easier to spot a few irregularities in those errors - there were spikes.&lt;/p&gt;

&lt;p&gt;And those spikes were happening during pod autoscaling:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/moz/glb-03.png&quot; alt=&quot;502 errors&quot; title=&quot;502 errors&quot; /&gt;
&lt;img src=&quot;/img/moz/glb-04.png&quot; alt=&quot;replica set scale down events&quot; title=&quot;Replica set scale down events&quot; /&gt;&lt;/p&gt;

&lt;p&gt;FirefoxCI cluster runs few HPA (Horizontal Pod Autoscalers) for &lt;strong&gt;ingress&lt;/strong&gt; and &lt;strong&gt;queue&lt;/strong&gt;, &lt;strong&gt;auth&lt;/strong&gt; services to scale pods up and down based on demand (CPU utilization).&lt;/p&gt;

&lt;p&gt;Those events happened frequently, which led to the termination of a big number of pods.&lt;/p&gt;

&lt;p&gt;Also it was quite easy to verify - disabling HPA showed that the number of errors was much lower.&lt;/p&gt;

&lt;h2 id=&quot;suspect-3---graceful-termination&quot;&gt;Suspect #3 - graceful termination&lt;/h2&gt;

&lt;p&gt;Pods being terminated is a usual thing, and they must be ready, at least in theory, to handle terminations gracefully.&lt;/p&gt;

&lt;p&gt;When kubernetes tells pod that it needs to be terminated (pod autoscaler requests this), it would send a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SIGTERM&lt;/code&gt; signal to the container and would wait at most &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terminationGracePeriodSeconds&lt;/code&gt;, before it would be forced killed.&lt;/p&gt;

&lt;p&gt;Kubernetes also allows running custom handlers when POD is about to be terminated. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lifecycle.preStop&lt;/code&gt; hook is being invoked before &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SIGTERM&lt;/code&gt; is sent.&lt;/p&gt;

&lt;p&gt;Flow here is:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;HPA requests deployment to be scale down (i.e. we don’t need 20 pods of this service, only 15)&lt;/li&gt;
  &lt;li&gt;Kubernetes decides which pods to terminate&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lifecycle.preStop&lt;/code&gt; hook is called&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SIGTERM&lt;/code&gt; signal is sent to container&lt;/li&gt;
  &lt;li&gt;Kubernetes waits for pod to finish up to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terminationGracePeriodSeconds&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Pod is being terminated&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Taskcluster up until this point didn’t define any custom &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;preStop&lt;/code&gt; hook, or didn’t handle &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SIGTERM&lt;/code&gt; gracefully, but rather just stopped the process, dropping all open connections.&lt;/p&gt;

&lt;p&gt;Nginx ingress handled it in some way, but that was not good enough, so few fixes were made:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/taskcluster/taskcluster/pull/6717&quot;&gt;#6717&lt;/a&gt; added &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SIGTERM&lt;/code&gt; listener in services in attempt to let existing connections finish before process is being killed&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/taskcluster/taskcluster/pull/6718&quot;&gt;#6718&lt;/a&gt; added &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lifecycle.preStop&lt;/code&gt; routine which is simply a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sleep 30s&lt;/code&gt; at that point.&lt;/li&gt;
  &lt;li&gt;ingress preStop hook improved to wait for all processes to finish serving requests:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Sleep XXs importance is as follows: Kubernetes controls the flow of incoming requests by using network endpoints. During lifecycle events (creation, termination), it would create or remove network endpoints for those pods. Due to the async nature of kubernetes events, during pod termination, it is not guaranteed that new requests will not be sent to a stopping pod right away. When we add &lt;code&gt;sleep xx&lt;/code&gt; to the preStop hook, we make sure that no requests are lost before the network endpoint finally disconnects that pod from new traffic. Existing connections would not be affected, and server would finish serving them after receiving the SIGTERM signal&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Nginx’s stopping procedure became:&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sleep &lt;/span&gt;5&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; nginx &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; quit&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;while &lt;/span&gt;pgrep &lt;span class=&quot;nt&quot;&gt;-x&lt;/span&gt; nginx&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sleep &lt;/span&gt;1&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;This was necessary because nginx spawns worker processes, and when main processes receive SIGTERM, it would not stop immediately, it would only notify workers. Checking if nginx processes are still running is a way to know that open connections are still being served&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Things start to look better after those fixes, total number of daily errors dropped even more:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/moz/glb-05.png&quot; alt=&quot;502 reduced with graceful termination&quot; title=&quot;502 reduced with graceful termination&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;further-steps&quot;&gt;Further steps&lt;/h3&gt;

&lt;p&gt;At this point it became really hard to figure out the root cause of the remaining errors. There were few assumptions:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Keepalive timeouts were too big or to small&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;preStop&lt;/code&gt; sleep time was too high or too low&lt;/li&gt;
  &lt;li&gt;GLB or backends configuration is not optimal&lt;/li&gt;
  &lt;li&gt;HorizontalPodAutscaler might be killing too many pods at once&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;more-observations&quot;&gt;More observations&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;HPA would scale down very fast and kill too many pods at once.&lt;/li&gt;
  &lt;li&gt;Containers (pods) are being restarted&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;suspect-4---container-restarts&quot;&gt;Suspect #4 - container restarts&lt;/h2&gt;

&lt;p&gt;Noticing that some spikes occur at a given time of the day and seeing the number of container restarts it became clear that this is an important factor, too.&lt;/p&gt;

&lt;p&gt;Turned out, that it was “by design” - &lt;a href=&quot;https://github.com/taskcluster/taskcluster/pull/1727/files&quot;&gt;#1727&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Assumption in the past was that containers should not be running for too long, and were forced to be restarted daily.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/taskcluster/taskcluster/issues/6759&quot;&gt;#6759&lt;/a&gt; fixes were made to address that.&lt;/p&gt;

&lt;h3 id=&quot;side-quest-achievement---websocktunnel-and-livelog&quot;&gt;Side quest achievement - websocktunnel and livelog&lt;/h3&gt;

&lt;p&gt;Looking at container restarts and lost connections, we were able to make a conclusion that &lt;strong&gt;websocktunnel&lt;/strong&gt; might be suffering from similar issues. &lt;a href=&quot;https://github.com/taskcluster/taskcluster/issues/6563&quot;&gt;#6563&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Websocktunnel&lt;/strong&gt; is being used to stream live logs from running tasks to the browser. Due to some memory leaks, that container would be restarted, and due to some other bugs in worker implementation, it would not properly connect back to the tunnel. Which means long-running tasks (like machine learning for translations that run for days) would be running without logs being visible.&lt;/p&gt;

&lt;p&gt;Increasing memory limits helped to avoid restarting containers too often, and made livelogs more stable. Restarts went down from multiple a day to once in 10-15 days.&lt;/p&gt;

&lt;h3 id=&quot;more-tweaks&quot;&gt;More tweaks&lt;/h3&gt;

&lt;p&gt;More kubernetes configuration and application fixes were added in &lt;a href=&quot;https://github.com/taskcluster/taskcluster/pull/6760&quot;&gt;#6760&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;suspect-5---node-autoscaling&quot;&gt;Suspect #5 - node autoscaling&lt;/h2&gt;

&lt;p&gt;It became apparent that the majority of remaining 502s are happening during cluster scaling events.&lt;/p&gt;

&lt;p&gt;GCP allows us to configure the cluster to have a variable number of nodes in the cluster, based on the current load.&lt;/p&gt;

&lt;p&gt;Observing logs revealed that this is happening quite frequently. Nodes are being added and then removed. During node draining and removal, more connections were still being lost.&lt;/p&gt;

&lt;p&gt;Few weeks of experimenting with timeouts, keepalives, grace termination periods and sleep delays couldn’t help reduce the number of errors.&lt;/p&gt;

&lt;p&gt;At this point I decided to disable this feature, and keep cluster running a fixed number of nodes. Cost savings from autoscaling were not substantial for FxCI, as it was mostly single node being added and removed during the day.&lt;/p&gt;

&lt;p&gt;Even GCP documentation suggests disabling this feature where dropped connections should be an issue.&lt;/p&gt;

&lt;p&gt;By disabling node autoscaling we were able to get rid of most of the &lt;strong&gt;502s&lt;/strong&gt; which means that clients would not be affected by random 30s-120s delays, and be forced to retry api calls. Which makes the whole system more reliable and resilient.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/moz/glb-06.png&quot; alt=&quot;Node autoscaling disabled&quot; title=&quot;Node autoscaling disabled&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;node-autoscaling-fix&quot;&gt;Node autoscaling fix&lt;/h2&gt;

&lt;p&gt;As the last piece of the puzzle, there were still few things we could try. One of them was &lt;a href=&quot;https://cloud.google.com/kubernetes-engine/docs/how-to/container-native-load-balancing&quot;&gt;container-native load balancing&lt;/a&gt; feature that allows GLB to send traffic directly to the pod which should receive the traffic, skipping the node instance group.&lt;/p&gt;

&lt;p&gt;This works on the service and ingress levels and to let Google Kubernetes Engine to know that some particular service should use Network Endpoint Groups (NEGs) and route traffic to &lt;strong&gt;pod&lt;/strong&gt; directly.&lt;/p&gt;

&lt;p&gt;To enable this feature service should have this annotation:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;cloud.google.com/neg&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{&quot;ingress&quot;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;true}&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once this is done, Load Balancer should start sending traffic directly to nginx pods, so in theory, when node scaling events happen, GLB would be aware which pods are healthy and which are not, and avoid sending traffic to the terminating pods.&lt;/p&gt;

&lt;p&gt;After 24h there were almost a hundred of scale up/down events, and &lt;strong&gt;zero&lt;/strong&gt; related &lt;strong&gt;502s&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/moz/glb-07.png&quot; alt=&quot;Node autoscaling events&quot; title=&quot;Node autoscaling events&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;some-final-tweaks&quot;&gt;Some final tweaks&lt;/h2&gt;

&lt;p&gt;Experimentally it was proven that following combination provided best results for FirefoxCI:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Pod’s &lt;strong&gt;terminationGracePeriodSeconds&lt;/strong&gt;: 210&lt;/li&gt;
  &lt;li&gt;Pod’s &lt;strong&gt;preStop&lt;/strong&gt; &lt;strong&gt;sleep: 120&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;GLB’s backend configuration: &lt;strong&gt;connectionDraining.drainingTimeoutSec: 60&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Pods are being killed too fast during downscaling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/taskcluster/taskcluster/pull/6824&quot;&gt;https://github.com/taskcluster/taskcluster/pull/6824&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;Major issues and findings were:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Application was not prepared to be killed gracefully&lt;/li&gt;
  &lt;li&gt;Nginx ingress was not waiting long enough for existing connections to be drained&lt;/li&gt;
  &lt;li&gt;GLBs configuration didn’t have draining timeout set&lt;/li&gt;
  &lt;li&gt;Autoscaling of pods and nodes were not configured optimally
    &lt;ol&gt;
      &lt;li&gt;Network Endpoint Groups were not configured to use container-native load balancing&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;Lack of proper E2E monitoring - application thinks the response was successful but client sees 5xx errors instead&lt;/li&gt;
  &lt;li&gt;Containers were forced-killed daily for no reason&lt;/li&gt;
&lt;/ol&gt;
</description>
        <pubDate>Fri, 15 Mar 2024 00:00:00 +0000</pubDate>
        <link>http://yaraslav.com//2024/03/15/gcp-load-balancer-502s.html</link>
        <guid isPermaLink="true">http://yaraslav.com//2024/03/15/gcp-load-balancer-502s.html</guid>
        
        <category>microservices</category>
        
        <category>cloud</category>
        
        <category>load balancer</category>
        
        <category>gcp</category>
        
        <category>taskcluster</category>
        
        <category>kubernetes</category>
        
        
      </item>
    
      <item>
        <title>ES modules. Migration of a large codebase</title>
        <description>&lt;p&gt;&lt;a href=&quot;https://github.com/taskcluster/taskcluster&quot;&gt;Taskcluster&lt;/a&gt; has a large codebase that is written in nodejs and consists of many components: database layer, internal libraries shared among microservices, and services themselves.&lt;/p&gt;

&lt;p&gt;They all use shared npm dependencies.&lt;/p&gt;

&lt;p&gt;To keep project up-to-date, we use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dependabot&lt;/code&gt;. However, more and more packages switch to ESM-only distributions, which makes it impossible to use in commonjs projects.&lt;/p&gt;

&lt;p&gt;ES Modules can import both ESM and CJS modules, but CJS can only import CJS.&lt;/p&gt;

&lt;h2 id=&quot;cjs-vs-esm&quot;&gt;CJS vs ESM&lt;/h2&gt;

&lt;p&gt;CommonJS modules usually look like this:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// package.json&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;index.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// index.js&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;foo&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&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;./foo&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;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// foo.js&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;foo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;foo&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;ESM modules look like this:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// package.json&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./index.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// index.js&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;foo&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./foo.js&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;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// foo.js&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;foo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;foo&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;migration&quot;&gt;Migration&lt;/h2&gt;

&lt;p&gt;In case of the example above, migration is straightforward:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Change or add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;type&quot;: &quot;module&quot;&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;package.json&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Replace all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;const x = require(y)&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import x from y&lt;/code&gt;
    &lt;ol&gt;
      &lt;li&gt;Make sure that files are referenced with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.js&lt;/code&gt; extension: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import x from &apos;./y.js&apos;&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;In case of folder &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import x from &apos;./y/index.js&apos;&lt;/code&gt;&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;Replace all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exports.x = y&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export const x = y&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Check possible problems with default vs named exports.&lt;/li&gt;
  &lt;li&gt;Replace &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__filename&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__dirname&lt;/code&gt; with import equivalents&lt;/li&gt;
  &lt;li&gt;Replace dynamic &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require(x)&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;await import(x)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Replace &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require(&apos;./data.json&apos;)&lt;/code&gt; with type assertions or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fs.readFile&lt;/code&gt; + &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JSON.parse&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Replace &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module.parent&lt;/code&gt; usage&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you have a larger codebase you could follow my steps from &lt;a href=&quot;https://github.com/taskcluster/taskcluster/pull/6603&quot;&gt;taskcluster migration&lt;/a&gt; PR.&lt;/p&gt;

&lt;h2 id=&quot;bulk-replace-all-imports&quot;&gt;Bulk replace all imports&lt;/h2&gt;

&lt;p&gt;There’s a great but abandoned utility &lt;a href=&quot;https://github.com/nolanlawson/cjs-to-es6&quot;&gt;cjs-to-es6&lt;/a&gt; which would help you to run the initial migration.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npx cjs-to-es6 ./services
npx cjs-to-es6 ./internal
// etc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It would do pretty good job already by replacing all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exports&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After that you would still need to go through all files and fix remaining issues:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;it doesn’t know if imported module is file or folder, so adding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.js&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/index.js&lt;/code&gt; would be up to you to fix&lt;/li&gt;
  &lt;li&gt;it doesn’t rewrite all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module.exports&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exports.xx&lt;/code&gt; magic that might be happening in the code&lt;/li&gt;
  &lt;li&gt;it doesn’t replace &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require()&lt;/code&gt; in the middle of your code with dynamic &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;named-vs-default-exports&quot;&gt;Named vs default exports&lt;/h2&gt;

&lt;p&gt;With Taskcluster internal libraries we kept both default and named imports to minimize the impact of the migration.&lt;/p&gt;

&lt;p&gt;However, for new code, named exports should be preferred, as it is easier to understand what is being imported.&lt;/p&gt;

&lt;p&gt;Consider default export example:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// foo.js&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;nx&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;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;nx&quot;&gt;arg&lt;/span&gt; &lt;span class=&quot;o&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;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// index.js&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;foo&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./foo.js&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;foo&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;c1&quot;&gt;// other.js&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;incrementor&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./foo.js&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;incrementor&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you want to refactor you would run into issue, because different files might have given different name for the default export and you would need to update all of them.&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;incrementor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;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;nx&quot;&gt;arg&lt;/span&gt; &lt;span class=&quot;o&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;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// index.js&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;incrementor&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./foo.js&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;incrementor&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;c1&quot;&gt;// other.js&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;incrementor&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./foo.js&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;incrementor&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Would make search and replace easier.&lt;/p&gt;

&lt;h2 id=&quot;fixing-dynamic-exports&quot;&gt;Fixing dynamic exports&lt;/h2&gt;

&lt;p&gt;Taskcluster used a lot of magic in testing library to mock dependencies for various tests.&lt;/p&gt;

&lt;p&gt;Something like this:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// helper.js&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;withServer&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;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;setup&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;server setup&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;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ApiClient&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;teardown&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;server teardown&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;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&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;c1&quot;&gt;// client_test.js&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;helper&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&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;./helper.js&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;suite&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;serverTest&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;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;helper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;withServer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;test&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;has server&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;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;helper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;server&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This kind of magic will not work with ESM as they strictly require to have exports to be statically analyzable.&lt;/p&gt;

&lt;p&gt;So one way to fix this issue is to return an object that would be modified by those helper functions,
or make such functions return unique object every time.&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// helper.js&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Approach 1&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;helper&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;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;helper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;helper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;withServer&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;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;setup&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;server setup&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;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;helper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ApiClient&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;teardown&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;server teardown&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;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;helper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This way in your tests you could import &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;helper&lt;/code&gt; and use it as before.&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// helper.js&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Approach 2&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;withServer&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;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;helper&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;nx&quot;&gt;setup&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;server setup&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;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;helper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ApiClient&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;teardown&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;server teardown&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;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;helper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&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;nx&quot;&gt;helper&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;// client_test.js&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;withServer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./helper.js&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;suite&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;serverTest&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;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;helper&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;withServer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;test&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;has server&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;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;helper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;server&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Both approaches are fine, but in case of Taskcluster it was easier to go with approach 1.&lt;/p&gt;

&lt;h2 id=&quot;importing-json-files&quot;&gt;Importing json files&lt;/h2&gt;

&lt;p&gt;Node.js allows to use &lt;a href=&quot;https://nodejs.org/api/esm.html#import-assertions&quot;&gt;import assertions&lt;/a&gt; since v17, but it is still marked as experimented
and you will likely see errors like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;(node:40419) ExperimentalWarning: Importing JSON modules is an experimental feature and might change at any time
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But you can still use them like this;&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;jsonSchemaDraft06&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;ajv/lib/refs/json-schema-draft-06.json&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;assert&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;type&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;json&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Alternatively, to avoid seeing warnings you could just use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fs&lt;/code&gt; to read file:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;jsonSchemaDraft06&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;readFileSync&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;ajv/lib/refs/json-schema-draft-06.json&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;utf8&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;__dirname-__filename&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__dirname&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__filename&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;Are gone, so if you need them, use &lt;a href=&quot;https://nodejs.org/api/esm.html#esm_import_meta&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import.meta&lt;/code&gt;&lt;/a&gt; instead:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__filename&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pathname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__dirname&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;URL&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;k&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pathname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;moduleparent-usage&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module.parent&lt;/code&gt; usage&lt;/h2&gt;

&lt;p&gt;Also no longer available and if you want to know if your module was imported or executed directly you could check same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import.meta.url&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// before&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;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;parent&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;// file was executed directly&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;crashOnError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&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;// after&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fileURLToPath&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&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;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;argv&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;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fileURLToPath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;url&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;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;crashOnError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&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;/div&gt;&lt;/div&gt;

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

&lt;p&gt;Migration to ESM is not that hard, but it is not as easy as just adding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;type&quot;: &quot;module&quot;&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;package.json&lt;/code&gt;.&lt;/p&gt;
</description>
        <pubDate>Tue, 17 Oct 2023 00:00:00 +0000</pubDate>
        <link>http://yaraslav.com//2023/10/17/esmodules.html</link>
        <guid isPermaLink="true">http://yaraslav.com//2023/10/17/esmodules.html</guid>
        
        <category>javascript</category>
        
        <category>taskcluster</category>
        
        
      </item>
    
      <item>
        <title>TIL: Prometheus Conference Berlin 2023</title>
        <description>&lt;p&gt;I didn’t attend &lt;a href=&quot;https://promcon.io/2023-berlin/&quot;&gt;PromCon Berlin 2023&lt;/a&gt; in person, but I watched &lt;a href=&quot;https://www.youtube.com/watch?v=pKYhMTJgJUU&quot;&gt;streams on the youtube&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Below are some new (for me) things, that are worth noting:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/perses/perses&quot;&gt;Perses&lt;/a&gt; is a dashboard for Prometheus (and later other sources) metrics&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://opentelemetry.io/&quot;&gt;OpenTelemetry&lt;/a&gt; is a standard for metrics, traces and logs&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/grafana/beyla&quot;&gt;Beyla&lt;/a&gt; automated application metrics with eBPF and Prometheus (more below)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://sustainable-computing.io/&quot;&gt;Kepler&lt;/a&gt; Kubernetes Efficient Power Level Exporter&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://platypusattack.com/&quot;&gt;Platypus&lt;/a&gt; attack. Power-side channel attacks on Intel CPUs. Guessing CPU instructions and operands by their memory consumption, and ability to extract crypto keys&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/cloud-carbon-footprint/cloud-carbon-footprint&quot;&gt;Cloud Carbon Footprint&lt;/a&gt; tool to estimate energy use and carbon emissions from public cloud usage&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/cncf/mentoring&quot;&gt;CNCF Mentoring Initiatives&lt;/a&gt; if anyone wants to do contribute and need help&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/autometrics-dev&quot;&gt;Autometrics&lt;/a&gt; add SLOs like success rate, latency, attach to your code and let it do the restore&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Few takeaways:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;create a tool (prometheus) and everyone would try to find an application for it, even if it is not always the right tool. Common issue.- create a tool and everyone would build 100x more tools around it. No wonder it’s so hard to figure out what’s good&lt;/li&gt;
  &lt;li&gt;measuring code metrics from inside the code is not right, as it doesn’t include kernel, IO, network timing and client side issues&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;beyla&quot;&gt;Beyla&lt;/h2&gt;

&lt;p&gt;Grafana’s &lt;a href=&quot;https://github.com/grafana/beyla&quot;&gt;beyla&lt;/a&gt; is a “eBPF-based auto-instrumentation of HTTP and HTTPS services”&lt;/p&gt;

&lt;p&gt;This is a quite novel and interesting approach to collect metrics from code without even touching the code. It does so by using &lt;a href=&quot;https://ebpf.io/&quot;&gt;eBPF&lt;/a&gt;.
My understanding of eBPF is that it is a set of kernel calls that allow user-space applications to get notified on various events that are happening inside kernel. For example listen to all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tcp_connect&lt;/code&gt; calls.
It is also language  (and version) specific though, intercepting those calls alone is not enough, you need to know memory layout to get list of arguments and context of that call.&lt;/p&gt;

&lt;p&gt;Beyla mainly supports GO code and some generic HTTP, HTTPS calls for other languages and can provide basic RED metrics (Request, Errors, Duration)&lt;/p&gt;

&lt;p&gt;It can send later all this events to prometheus directly.
User-space monitoring program requires &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CAP_SYS_ADMIN&lt;/code&gt; privileges.&lt;/p&gt;

&lt;h2 id=&quot;greenops&quot;&gt;GreenOps&lt;/h2&gt;

&lt;p&gt;It is really hard to measure impact of data centers, and cloud services provide limited carbon monitoring.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://sustainable-computing.io/&quot;&gt;Kepler&lt;/a&gt; Kubernetes-based Efficient Power Level Exporter. It uses RAPL (Intel-based Running Average Power Limit) feature that reports energy consumption. Which later uses eBPF to attribute power usage to process and then to pod. So you could later aggregate by namespace/deployment/service.&lt;/p&gt;

&lt;p&gt;Downside is that RAPL not accessible in VMs and is the source of fantastic vulnerability discovered in &lt;a href=&quot;https://platypusattack.com/&quot;&gt;Platypus&lt;/a&gt; attack.&lt;/p&gt;

&lt;p&gt;Platypus attack is a brilliant and creative way to sample power usage and associate it with unique processor instruction (different instructions consume different amount of power). It does also allow to get the Hamming distance of its operands (number of non-zero bits). Having this info it is “really easy” to extract information like crypto keys from memory. Crazy.&lt;/p&gt;

&lt;p&gt;If you want to measure your impact you could use &lt;a href=&quot;https://www.cloudcarbonfootprint.org/&quot;&gt;Cloud Carbon Footprint&lt;/a&gt; to get those estimates.
Here’s &lt;a href=&quot;https://demo.cloudcarbonfootprint.org/&quot;&gt;demo&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;autometrics&quot;&gt;Autometrics&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/autometrics-dev&quot;&gt;Autometrics&lt;/a&gt; look interesting too. It is a micro-framework which uses decorators to wrap code and extract metrics automatically.
&lt;a href=&quot;https://github.com/autometrics-dev/autometrics-shared/blob/main/SPEC.md&quot;&gt;Spec here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What I find interesting is that it allows to provide objectives (SLOs) in form of success rate, latency. Besides that, it generates promql queries to query those metrics in provided Grafana dashboards.&lt;/p&gt;

&lt;p&gt;“Auto” in name might be misleading, because it still requires adding a library, making changes to code and providing a config.&lt;/p&gt;
</description>
        <pubDate>Sat, 30 Sep 2023 00:00:00 +0000</pubDate>
        <link>http://yaraslav.com//2023/09/30/prometheus-conf.html</link>
        <guid isPermaLink="true">http://yaraslav.com//2023/09/30/prometheus-conf.html</guid>
        
        <category>til</category>
        
        <category>prometheus</category>
        
        <category>grafana</category>
        
        
      </item>
    
      <item>
        <title>One year at mozilla</title>
        <description>&lt;p&gt;My last post on this topic &lt;a href=&quot;/2022/02/14/one-month-at-mozilla&quot;&gt;one month at Mozilla&lt;/a&gt; was posted as the title suggests when I just started.&lt;/p&gt;

&lt;p&gt;My contribution at that point was a single &lt;a href=&quot;https://github.com/taskcluster/taskcluster/pull/5163&quot;&gt;pull request&lt;/a&gt; as I was struggling to understand what is happening and how things work.&lt;/p&gt;

&lt;p&gt;Since then, I was able to do a bit more and here is a brief summary of my first year at Mozilla:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;submitted &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;119&lt;/code&gt; PRs in &lt;a href=&quot;https://github.com/search?q=org%3Ataskcluster+is%3Apr+author%3Alotas&amp;amp;type=pullrequests&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;taskcluster&lt;/code&gt;&lt;/a&gt; project.&lt;/li&gt;
  &lt;li&gt;created &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;40&lt;/code&gt; &lt;a href=&quot;https://github.com/search?q=org%3Ataskcluster+is%3Apr+author%3Alotas&amp;amp;type=issues&quot;&gt;issues&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;helped shipping &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3&lt;/code&gt; &lt;a href=&quot;https://github.com/taskcluster/taskcluster/releases&quot;&gt;major versions&lt;/a&gt; to &lt;a href=&quot;https://firefox-ci-tc.services.mozilla.com/&quot;&gt;Firefox-CI&lt;/a&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;75&lt;/code&gt; &lt;a href=&quot;https://github.com/taskcluster/taskcluster/commits/main/CHANGELOG.md&quot;&gt;minor + patch&lt;/a&gt; releases&lt;/li&gt;
  &lt;li&gt;fixed worker-manager bug that drove everyone crazy&lt;/li&gt;
  &lt;li&gt;sent first &lt;a href=&quot;https://hg.mozilla.org/integration/autoland/rev/e6925f98d5d9&quot;&gt;patch&lt;/a&gt; to Firefox (&lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1807667&quot;&gt;1807667&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;attended or lead &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;27&lt;/code&gt; &lt;a href=&quot;https://docs.google.com/document/d/1dCQScxwH04VInIntxesYVQ8te1IvLTZXWYrHNgi1Irc/view&quot;&gt;Taskcluster weekly Community&lt;/a&gt; meetings&lt;/li&gt;
  &lt;li&gt;improved developer experience&lt;/li&gt;
  &lt;li&gt;was on approx &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;350+&lt;/code&gt; calls (according to &lt;a href=&quot;https://www.raycast.com/changelog/1-45-0&quot;&gt;Raycast wrapped&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;met with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;800+&lt;/code&gt; mozillians at the annual All-Hands&lt;/li&gt;
  &lt;li&gt;visited Berlin office &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;10+&lt;/code&gt; times&lt;/li&gt;
  &lt;li&gt;ordered &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;25+&lt;/code&gt; books of which managed to finish maybe &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;5&lt;/code&gt; for now&lt;/li&gt;
  &lt;li&gt;learned truly a lot from the people who I work with or who worked there before me&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Overall, I feel like this was a very productive year for me, despite all my fears and concerns at the beginning, when I was trying to figure things out.&lt;/p&gt;

&lt;p&gt;That, of course, came with big support from the people I work with, big thanks and credits to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/petemoore&quot;&gt;Pete Moore&lt;/a&gt; great patience and discussions about the golden age of 8-bit computing&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/matt-boris&quot;&gt;Matt Boris&lt;/a&gt; mastering dependabot and node/go upgrades&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/mostlygeek&quot;&gt;Benson Wong&lt;/a&gt; guiding me on what to focus on&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/leplatrem/&quot;&gt;Mathieu Leplatre&lt;/a&gt; reminded me how to use mercurial to contribute to Firefox&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/grahamalama&quot;&gt;Graham Beckley&lt;/a&gt; for sharing thoughtful articles, ideas and music&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/bsieber-mozilla&quot;&gt;Bryan Sieber&lt;/a&gt; for being goal-oriented&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are also many people outside of my team who influenced me in a positive way:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/Eijebong&quot;&gt;Bastien Orivel&lt;/a&gt; from community who contributes quite a lot to the Taskcluster project&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/djmitche&quot;&gt;Dustin Mitchell&lt;/a&gt; is a former Mozilla employee who keeps writing code even when he sleeps&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://jonasfj.dk/&quot;&gt;Jonas Finnemann Jensen&lt;/a&gt; was one of the authors of Taskcluster, created few cool things while at Mozilla&lt;/li&gt;
  &lt;li&gt;whole RelEng team including &lt;a href=&quot;https://github.com/escapewindow&quot;&gt;Aki Sasaki&lt;/a&gt;, &lt;a href=&quot;https://github.com/ahal&quot;&gt;Andrew Halberstadt&lt;/a&gt;, &lt;a href=&quot;https://github.com/JohanLorenzo&quot;&gt;Johan Lorenzo&lt;/a&gt; who support my team and provide valuable insights to the Taskcluster project.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/MasterWayZ&quot;&gt;Michelle&lt;/a&gt; for trying to deploy Taskcluster the “hard way”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I had to spend some time to collect various bugs and issues that were important at that time. Here is that &lt;a href=&quot;https://gist.github.com/lotas/0cef453c427d033f394de5dd1ee76e56&quot;&gt;list&lt;/a&gt;. This list is huge, so we had to prioritize and decide what to work on. Below I list few achievements that I was able to accomplish.&lt;/p&gt;

&lt;h3 id=&quot;most-notable-achievements&quot;&gt;Most notable achievements&lt;/h3&gt;

&lt;!-- TOC --&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#ui-tests&quot;&gt;UI tests&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#firefox-remote-settings-tests&quot;&gt;Firefox Remote Settings tests&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#dev-deployment&quot;&gt;Dev deployment&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#worker-manager-slowness&quot;&gt;Worker Manager “slowness”&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#continuous-deployment&quot;&gt;Continuous Deployment&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#developer-experience&quot;&gt;Developer experience&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#metrics-and-visualisations&quot;&gt;Metrics and visualisations&lt;/a&gt;
&lt;!-- /TOC --&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When I joined Taskcluster project it was mostly in a maintenance mode. Most of the original team left after unfortunate round of layoffs or its aftermath few years ago. The knowledge of the system was mostly lost or existed in some pieces in numerous places. &lt;a href=&quot;https://firefox-ci-tc.services.mozilla.com/&quot;&gt;Firefox CI&lt;/a&gt; which is the main deployment of Taskcluster that runs thousands of millions of tasks was not updated for many months and was haunted by some bugs that made whole service feel pretty buggy and slow.&lt;/p&gt;

&lt;p&gt;Taskcluster consists of several micro-services that exist in the same &lt;a href=&quot;https://github.com/taskcluster/taskcluster&quot;&gt;monorepo&lt;/a&gt; and it had few issues that a new person joining this project might have:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You could only run one or few micro-services locally while you do your development. You need to start each one manually, do some changes, add some tests, hope for the best and push.&lt;/li&gt;
  &lt;li&gt;It was not possible to test your changes on some environment before you make a release. So often we would need to deploy release to be able to test it, and then ship few hotfixes right after.&lt;/li&gt;
  &lt;li&gt;Dependabot sent many PRs to update UI packages, but no one knew if those will work because tests were not present&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;ui-tests&quot;&gt;UI tests&lt;/h4&gt;

&lt;p&gt;To gain more confidence in frontend dependency upgrades I’ve switched testing from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mocha&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jest&lt;/code&gt; and improved coverage for most of the utils functions and few components.
I also added &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;e2e&lt;/code&gt; tests with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cypress&lt;/code&gt;. But the problem with E2E was that at this time I didn’t know how the system works or what to test, so it really covered just a simple case, and is still not running as part of CI jobs.&lt;/p&gt;

&lt;p&gt;Having more tests helped to be slightly more confident in the merged dependency upgrades.&lt;/p&gt;

&lt;h4 id=&quot;dev-deployment&quot;&gt;Dev deployment&lt;/h4&gt;

&lt;p&gt;Although Taskcluster had everything it needed documented and had the right set of tools, it was still quite tricky to deploy your own Taskcluster instance in the Kubernetes cluster.&lt;/p&gt;

&lt;p&gt;Pre-conditions where to set up RabbitMQ instance and get the credentials, create Postgres DB instance in Cloud SQL, provision static IP and load balancer, manually issue certificate (which will later expire and you’d have to upload renewed one manually). Next you would have to manually configure DNS entries in Route53 to point to your dev deployment&lt;/p&gt;

&lt;p&gt;Then, having all those necessary credentials you could &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yarn run dev:init&lt;/code&gt; that will ask few questions and generate big &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dev-config.yml&lt;/code&gt; containing all necessary values for the kubernetes templates that would later be deployed with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yarn run dev:apply&lt;/code&gt; command. If you made a mistake, you’d have to repeat that process again, and some config steps were not obvious.&lt;/p&gt;

&lt;p&gt;In order to simplify this I did few improvements:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;patched config tool and documentation on how to properly generate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dev-config.yml&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;added new utility tools: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dev:ensure:db&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dev:ensure:rabbit&lt;/code&gt; to allow re-running some config steps separately, without the need to run whole config again&lt;/li&gt;
  &lt;li&gt;tested and documented how to install both &lt;a href=&quot;https://github.com/taskcluster/taskcluster/blob/main/dev-docs/dev-deployment.md#own-rabbitmq-in-cluster&quot;&gt;rabbit&lt;/a&gt; and &lt;a href=&quot;https://github.com/taskcluster/taskcluster/blob/main/dev-docs/dev-deployment.md#own-postgres-in-cluster&quot;&gt;postgres&lt;/a&gt; into the cluster with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;helm&lt;/code&gt; to be able to script all those steps without the need to use external services. In the end you could run one script to install rabbit, postgres and use those for the deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Still, the problem was to provision static IP, Load Balancer and create certificates manually. For this I’ve added &lt;a href=&quot;https://github.com/taskcluster/taskcluster/blob/main/dev-docs/dev-deployment.md#ingress-nginx&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ingress-nginx&lt;/code&gt; support&lt;/a&gt; and &lt;a href=&quot;https://github.com/taskcluster/taskcluster/blob/main/dev-docs/dev-deployment.md#cert-manager&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cert-manager&lt;/code&gt;&lt;/a&gt; to use LetsEncrypt to provision certificates automatically.&lt;/p&gt;

&lt;p&gt;I was also able to configure a wildcard DNS to point to our single static IP on dev cluster so anyone could deploy his dev instance easily.&lt;/p&gt;

&lt;p&gt;Major benefit was that we no more had to manually provision static IP, certificate and load balancer. By getting rid of GLB and using nginx-ingress we got more flexibility during deployments and re-deployments and saved time (no need to wait 5-10 minutes for GLB to pick up “healthy backends”). Provisioning of certificates was automated with adding few annotations and metadata to the config files.&lt;/p&gt;

&lt;h4 id=&quot;worker-manager-slowness&quot;&gt;Worker Manager “slowness”&lt;/h4&gt;

&lt;p&gt;No argue that Taskcluster is a very complex system that went through a lot of changes to get where it is today. One of the problems was to run thousands of tasks for any change you make in &lt;a href=&quot;https://hg.mozilla.org/mozilla-central/&quot;&gt;Firefox source code&lt;/a&gt;. Having multiple contributors results in tens and hundreds of thousands of tasks that needs to be executed somewhere.&lt;/p&gt;

&lt;p&gt;Those tasks are executed by workers (&lt;a href=&quot;https://github.com/taskcluster/taskcluster/tree/main/workers/docker-worker&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-worker&lt;/code&gt;&lt;/a&gt; or &lt;a href=&quot;https://github.com/taskcluster/taskcluster/tree/main/workers/generic-worker&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;generic-worker&lt;/code&gt;&lt;/a&gt;) that are running on any virtual or physical device. And the best way to do this was to create those machines on demand, based on the load.&lt;/p&gt;

&lt;p&gt;Initial attempts were &lt;a href=&quot;https://github.com/taskcluster/aws-provisioner&quot;&gt;aws-provisioner&lt;/a&gt;) and &lt;a href=&quot;https://github.com/taskcluster/ec2-manager&quot;&gt;ec2-provisioner&lt;/a&gt;. Which were later replaced by &lt;a href=&quot;https://github.com/taskcluster/taskcluster/tree/main/services/worker-manager&quot;&gt;worker-manager&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Taskcluster has &lt;strong&gt;worker pools&lt;/strong&gt;. Each &lt;strong&gt;worker pool&lt;/strong&gt; belongs to provider (AWS, Azure, GCP, static) and specifies how new instances should be created (AMI, config, timeouts, etc). Each &lt;strong&gt;task&lt;/strong&gt; is being claimed by a &lt;strong&gt;worker&lt;/strong&gt; from specified &lt;strong&gt;worker pool&lt;/strong&gt; (i.e. I want &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;taskA&lt;/code&gt; to run on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;google/ubuntu-18.04&lt;/code&gt; worker pool).&lt;/p&gt;

&lt;p&gt;This way worker-manager can constantly check every worker pool to see if it has new tasks. Then using some &lt;a href=&quot;https://github.com/taskcluster/taskcluster/blob/main/services/worker-manager/src/estimator.js&quot;&gt;simple estimations&lt;/a&gt; it can decide if new instances are needed, or existing capacity is enough. If new instances are needed, it will &lt;em&gt;provision&lt;/em&gt; up to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;maxCapacity&lt;/code&gt; instances. Instances will be launched, workers would connect to Taskcluster and start executing those tasks, shutting themselves down once there are no new tasks.&lt;/p&gt;

&lt;p&gt;Every once in a while, worker-manager would scan all workers that it started to see if they are still fine or need to be shut down.&lt;/p&gt;

&lt;p&gt;So essentially, worker-manager consists of &lt;a href=&quot;https://github.com/taskcluster/taskcluster/blob/main/services/worker-manager/src/provisioner.js&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;provisioner&lt;/code&gt;&lt;/a&gt; loop (deciding how many instances to start) and &lt;a href=&quot;https://github.com/taskcluster/taskcluster/blob/main/services/worker-manager/src/worker-scanner.js&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;worker-scanner&lt;/code&gt;&lt;/a&gt; loop (checking running instances status, stopping).&lt;/p&gt;

&lt;p&gt;All works good … before the loop times out:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/moz/wm-before.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The red dots above says that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;worker-scanner&lt;/code&gt; loop was timing out after about 2.5 hours. This means that not all of the workers could been checked, so system doesn’t know what state the worker is or to even know the exact amount of running capacity.&lt;/p&gt;

&lt;p&gt;Digging into the the timeouts I’ve discovered that most of those delays come from one single provider (starts with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A&lt;/code&gt; and ends with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zure&lt;/code&gt;). And when you do thousands and thousands of calls daily you would inevitably run into all sorts of issues. But somehow it was only Azure.&lt;/p&gt;

&lt;p&gt;How many API calls do you need to start new virtual machine? &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AWS&lt;/code&gt; - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GCP&lt;/code&gt; - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Azure&lt;/code&gt; - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3+&lt;/code&gt;! Yes, at least 3 calls to create a virtual machine (provision IP, provision NIC, provision Disks (optional), provision VM). Not to mention that Azure API is not very reliable.. calls take different amount of time to complete and sometimes hang indefinitely, which made us implement few timeouts to kill the calls that take too long (speaking of 10m+ here). And to add to this, provisioning of a single resource (pi/nic/disk/vm) is done in async manner, which means you need to: 1. request resource and get operation id 2. query if the operation with given id completed. So to provision IP/NIC/VM you need at least 6+ API calls. (Why Azure? WHY?)&lt;/p&gt;

&lt;p&gt;Let’s get back to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;worker-scanner&lt;/code&gt; who tries to do as much as possible as fast as possible. What happens is that it goes through every worker pool and checks all workers within that pool. When it gets to the Azure pools and some calls begin to &lt;em&gt;take longer&lt;/em&gt; it cannot process other pools, times out and dies. Simply making this process parallel wouldn’t help, because you can easily hit rate limits and it would be even a bigger issue. Having single loop helps to control backoff timeouts and retries for cloud api calls easier.&lt;/p&gt;

&lt;p&gt;So all of those timed out loops led to few workers being created on time, which led to stuck builds and hours and hours of waiting time to deliver anything. And if worker fails to start within a defined timeframe it will be killed. No wonder everyone was frustrated and overall impression was quite bad, but no one could figure out why everything is so slow.&lt;/p&gt;

&lt;p&gt;There were few different solutions proposed in the past to this problem, like combining scanner and provisioner into a single loop. But this still would have an issue with timing out azure calls.&lt;/p&gt;

&lt;p&gt;Without understanding how to fix this issue at the time, my first take was to separate &lt;em&gt;bad&lt;/em&gt; providers into own isolated loop, that I could examine separately to figure out how to fix its slowness. So instead of merging both loops, I split it even more with creating &lt;a href=&quot;https://github.com/taskcluster/taskcluster/pull/5343&quot;&gt;separate azure scanner&lt;/a&gt; job.&lt;/p&gt;

&lt;p&gt;Next small improvements where to realize that not all workers need Public IP, so we could skip IP provisioning and reduce total number of calls. Here we go, &lt;a href=&quot;https://github.com/taskcluster/taskcluster/commit/99f5978c6152e52a246b7f3d6db80d9fa7d8fc79&quot;&gt;skipPublicIP&lt;/a&gt; was added.
Some calls take forever? Okay, let’s &lt;a href=&quot;https://github.com/taskcluster/taskcluster/pull/5436&quot;&gt;time cap it at 10min&lt;/a&gt;, which is insanely too much anyways.
And then optimize it a bit by &lt;a href=&quot;https://github.com/taskcluster/taskcluster/pull/5382/&quot;&gt;requesting first resource&lt;/a&gt; from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;provisioner&lt;/code&gt; loop immediately.&lt;/p&gt;

&lt;p&gt;I’ve added &lt;a href=&quot;https://github.com/taskcluster/taskcluster/blob/main/services/worker-manager/README.md#worker-manager-lifecycle&quot;&gt;few diagrams&lt;/a&gt; in the docs to display whole process. Check how insanely complex &lt;a href=&quot;https://github.com/taskcluster/taskcluster/blob/main/services/worker-manager/README.md#azure-specific-checks&quot;&gt;Azure checkWorker&lt;/a&gt; is.&lt;/p&gt;

&lt;p&gt;Huh, alright.. so what good or bad did those changes do?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/moz/wm-after.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Turned out, that as soon as Azure pools were kicked out of the main loop, it automatically solved all the issues! Miracle. Worker scanner was not timing out anymore (or at least did it way less often), which means that AWS and GCP workers could start instantly now and serve incoming numbers of tasks. But also isolated Azure loop was seeing much less timeouts and was working more predictably.&lt;/p&gt;

&lt;p&gt;After few weeks everyone who considered Taskcluster slow and buggy suddenly forgot about those issues. Tasks were being run on time, birds were signing, grass was green and sun was bright..&lt;/p&gt;

&lt;p&gt;I was just lucky to be able to solve this issue in a relatively simple manner. Understanding and getting down to that solution took me few handful of weeks though.&lt;/p&gt;

&lt;h4 id=&quot;continuous-deployment&quot;&gt;Continuous Deployment&lt;/h4&gt;

&lt;p&gt;Ok, so as the main problem was solved, it was time to continue solving the problem of testing before the release is done. For this I experimented with GCP’s Cloud Build and it proved to be working great. Each new commit to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt; branch triggered build job in Cloud Build. New docker image would be built and stored in Cloud Registry.&lt;/p&gt;

&lt;p&gt;Having up-to-date docker image for the main branch means that we can now use it to deploy to our dev environment. Using previously implemented changes it was trivial to create new environment &lt;a href=&quot;https://dev.alpha.taskcluster-dev.net/&quot;&gt;https://dev.alpha.taskcluster-dev.net/&lt;/a&gt; that will use ingress-nginx, cert-manager and deploy change there.&lt;/p&gt;

&lt;p&gt;Later &lt;a href=&quot;https://github.com/matt-boris&quot;&gt;Matt Boris&lt;/a&gt; helped to create &lt;a href=&quot;https://github.com/taskcluster/taskcluster/blob/main/cloudbuild.yaml&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cloudbuild.yaml&lt;/code&gt;&lt;/a&gt; and put steps definitions in there. It could build new image, deploy it, remove older images as we no longer need them and run smoketests.&lt;/p&gt;

&lt;p&gt;This was a huge leap forward because it allowed us to experiment and test features even before they would be released. And this happens within 10-15 minutes after branch gets merged to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt;.&lt;/p&gt;

&lt;h4 id=&quot;developer-experience&quot;&gt;Developer experience&lt;/h4&gt;

&lt;p&gt;Having own dev environment was already a big win, but still, it required merging your changes into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt; branch and waiting for the build to finish. So I started to build a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose&lt;/code&gt; approach, that would allow to run all or required services locally and develop all or many services at once. This promised to be a huge win, because it would eliminate the need to build and deploy at all. You could just run everything locally.&lt;/p&gt;

&lt;p&gt;However, it took &lt;a href=&quot;https://github.com/taskcluster/taskcluster/pull/5430&quot;&gt;more than one month&lt;/a&gt; to get there. Because every service has its own set of configuration options, own client ids, own credentials to access database, queue, apis, it required a lot of trial and error attempts to make it right. Huge credits go to &lt;a href=&quot;https://github.com/Eijebong&quot;&gt;Bastien&lt;/a&gt; for his contributions. He was able to provide valuable comments and patches to make things work together. Setting up local S3 storage with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;minio&lt;/code&gt;, showing few tricks how to use healthcheck conditions properly (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;service_healthy&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;service_completed_successfully&lt;/code&gt;), and how to set up &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;minio&lt;/code&gt; and ingress properly.&lt;/p&gt;

&lt;p&gt;Still, to run everything locally you’d need a lot of CPU and memory, as in addition to running main services, there were tens of background jobs. Sure, you don’t need all of them to run locally, but also I had no idea how to make services optional in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose.yml&lt;/code&gt; so they wouldn’t be starting with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker compose up&lt;/code&gt;. Luckily it was a solved problem, as I discovered profiles, which I described in &lt;a href=&quot;/2022-08-14-docker-compose-profiles.html&quot;&gt;this post&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So you could easily start just the API workers, and additionally include some background jobs with profiles as needed.&lt;/p&gt;

&lt;p&gt;Next &lt;a href=&quot;https://github.com/matt-boris&quot;&gt;Matt Boris&lt;/a&gt; helped to add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose.dev.yml&lt;/code&gt; and necessary &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yarn&lt;/code&gt; scripts so we could run some services in production mode and some in development.&lt;/p&gt;

&lt;p&gt;By adding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-devel&lt;/code&gt; image with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nodemon&lt;/code&gt; it was possible to map local volumes and make dev containers to reload once the source code changed. (This is still an issue for UI though, as webpack inside docker works extremely slow, so running UI natively is a better approach for now)&lt;/p&gt;

&lt;p&gt;This allowed to have full local development environment. Which included the UI, where you could login, create tasks, do everything as in production environments, call API endpoints and run tasks.&lt;/p&gt;

&lt;p&gt;Running tasks locally proved to be a harder task, as it required &lt;a href=&quot;https://github.com/taskcluster/taskcluster/commit/1514ffd2ab57a78e320ef383644f14066210b9ec&quot;&gt;containerizing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;generic-worker&lt;/code&gt;&lt;/a&gt; and bundling all its dependencies. In the end it was possible to make it running along with the services using same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker compose up&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Having those pieces of puzzle in place, made it possible for other teams to contribute easier.
&lt;a href=&quot;https://ahal.ca/blog/2022/taskcluster-github-dev/&quot;&gt;Andrew Halberstadt&lt;/a&gt; was able to implement changes to the &lt;a href=&quot;https://github.com/taskcluster/taskcluster/tree/main/services/github&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github service&lt;/code&gt;&lt;/a&gt; using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ngrok&lt;/code&gt; to forward webhooks to the local services he was running with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker compose&lt;/code&gt;.&lt;/p&gt;

&lt;h4 id=&quot;metrics-and-visualisations&quot;&gt;Metrics and visualisations&lt;/h4&gt;

&lt;p&gt;Alright, having those problems solved I was able to have some fun and add few missing features.&lt;/p&gt;

&lt;h5 id=&quot;dashboard&quot;&gt;Dashboard&lt;/h5&gt;

&lt;p&gt;Taskcluster is big but you don’t know what’s happening inside. It is a distributed system and you don’t know that something is happening, or know what exactly is happening there.&lt;/p&gt;

&lt;p&gt;I wanted to be able to see a few stats. So I added some stats to the dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://community-tc.services.mozilla.com/&quot;&gt;Community TC&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/moz/dashboard-community.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://firefox-ci-tc.services.mozilla.com/&quot;&gt;Firefox CI&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/moz/dashboard-fxci.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Good, it is helpful to see how many pending tasks you have now, or how many workers were requested, or are running at the moment.&lt;/p&gt;

&lt;h5 id=&quot;timing&quot;&gt;Timing&lt;/h5&gt;

&lt;p&gt;Let’s move on to the tasks. As I mentioned somewhere above, each push to &lt;strong&gt;mozilla-central&lt;/strong&gt; can trigger thousands of builds. Each single push results in a Task Group being created that consists of all the tasks.&lt;/p&gt;

&lt;p&gt;If you opened &lt;a href=&quot;https://firefox-ci-tc.services.mozilla.com/tasks/groups/RwbUjGCRSx6AzMTvKecoiw&quot;&gt;Task Group&lt;/a&gt; page before you would see how many tasks are in Completed state, how many Failed, how many Running. But you were not able to see which task is the longest to run? Or how long does it take to finish some task without going into each task individually.&lt;/p&gt;

&lt;p&gt;Okay, so I want to open the Task Group page and see how long did it take for each task to complete, what was the longest one, how long it took the whole graph to resolve?&lt;/p&gt;

&lt;p&gt;This was the first take:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/moz/group-stats.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I say “first” because the information presented here only gives answers to “how long it took to resolve”. Ideally it should also include information on how long do tasks need to wait before they are being executed. If tasks depend on each other it would be good to show who is waiting the longest, and few other things.&lt;/p&gt;

&lt;p&gt;With this information you can already understand a lot. Sort by duration and see how some windows tests run for two hours. See the distribution of task times on a graph. Hover quickly to see which tasks were failing and how long it took for all tasks to complete.&lt;/p&gt;

&lt;p&gt;It revealed that some groups might have more than a thousand compute hours to resolve. That is a lot!&lt;/p&gt;

&lt;h4 id=&quot;firefox-remote-settings-tests&quot;&gt;Firefox Remote Settings tests&lt;/h4&gt;

&lt;p&gt;This one is rather funny and is a good example that you should always fix the root cause of the issue.&lt;/p&gt;

&lt;p&gt;I’ve discovered that Taskcluster UI serves millions of calls a week to the unknown URLs: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/api/remote-settings-dummy/v1&lt;/code&gt; .. which did not belong to Taskcluster. And because default backend is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ui&lt;/code&gt; which runs nginx that serves all requests to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index.html&lt;/code&gt; which respond with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;200&lt;/code&gt; to all requests.&lt;/p&gt;

&lt;p&gt;My first thought was to add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;404&lt;/code&gt; response status on nginx level to all endpoints that don’t belong there. But this will not fix the problem, calls would still be made. Luckily, &lt;a href=&quot;https://github.com/petemoore&quot;&gt;Pete&lt;/a&gt; discovered that all those URLs are defined in the Firefox source code. Test profiles used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost/api/remote-settings-dummy*&lt;/code&gt; endpoints. Somebody wanted to avoid doing real network calls in tests and thought that this would be enough.&lt;/p&gt;

&lt;p&gt;However, the way that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-worker&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;generic-worker&lt;/code&gt; work, they expose &lt;a href=&quot;https://github.com/taskcluster/taskcluster/tree/main/tools/taskcluster-proxy&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;taskcluster-proxy&lt;/code&gt;&lt;/a&gt; on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://taskcluster&lt;/code&gt; host that enables easy way for tasks to communicate with Taskcluster (to fetch secrets, artifacts and create new tasks). However, this domain is mapped to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;127.0.0.1&lt;/code&gt; by default. And what happened that all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost/&lt;/code&gt; requests were also served by this proxy and forwarded back to the Taskcluster deployment.&lt;/p&gt;

&lt;p&gt;That was my chance after almost a year in Mozilla to finally contribute to Firefox. Luckily, main developer of Remote Settings, &lt;a href=&quot;https://github.com/leplatrem/&quot;&gt;Mathieu Leplatre&lt;/a&gt; was from my team, and he was happy to help me to submit my patch. Was a cool experiment, as the last time I used mercurial was around 2010 or so.&lt;/p&gt;

&lt;p&gt;Here was &lt;a href=&quot;https://phabricator.services.mozilla.com/D165552&quot;&gt;this patch&lt;/a&gt; that triggered more discussions from the reviewers and I was able to patch all places and make sure things work as expected after those changes. So instead of changing two files as I initially thought, I had to change a bunch of related files and updates few tests. This was during the Christmas holidays, so it took a bit longer. Thanks to &lt;a href=&quot;https://phabricator.services.mozilla.com/p/robwu/&quot;&gt;Rob Wu&lt;/a&gt; for being patient with the reviews.&lt;/p&gt;

&lt;p&gt;The fix &lt;a href=&quot;https://hg.mozilla.org/mozilla-central/rev/e6925f98d5d9&quot;&gt;landed&lt;/a&gt; into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;110.0a1&lt;/code&gt; release.&lt;/p&gt;

&lt;p&gt;Few days later I started to observe less and less calls to Taskcluster UI, and eventually numbers went from millions a week to just few thousands. There are still some older branches people develop against, it will take time for everyone to fetch latest changes. But to be fair, I have no idea how many developers work on Firefox source code, because you can checkout clean branch, apply few changes, and be behind 100+ commits just few hours later. Lots of things happening there.&lt;/p&gt;

&lt;p&gt;Bonus win here came few days later as someone reported &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;7%&lt;/code&gt;..&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;11%&lt;/code&gt; improvement in test times. Ha! We removed network calls and now the test would finish &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;6s..16s&lt;/code&gt; faster. Awesome. Considering absolute numbers of those tests, total gain would be quite impressive. Possibly few hours to days of compute resources saved daily or weekly.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/moz/perftest.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;what-could-be-done-better&quot;&gt;What could be done better?&lt;/h3&gt;

&lt;p&gt;Of course, mentioning just the good parts wouldn’t be fair and would mean that there were no downsides or disappointments. I’ll list some of them here.&lt;/p&gt;

&lt;p&gt;It took me way longer to understand how Taskcluster works, and I still don’t fully understand it. It took me about two month to find to start delivering valuable patches.&lt;/p&gt;

&lt;p&gt;I didn’t write nearly enough blog posts as I was hoping to. I had plans to write series of posts describing how Taskcluster works. So this is the first post, but hopefully not the last one on the topic.&lt;/p&gt;

&lt;p&gt;I’ve started with E2E tests but didn’t manage to finish those. And they are not being used at the moment. Should find some time and cover more workflows and then make it run on each build.&lt;/p&gt;

&lt;p&gt;I saw few gaps in &lt;a href=&quot;https://docs.taskcluster.net&quot;&gt;documentation&lt;/a&gt; and was planning to extend it with more topics, but only managed to cover one &lt;a href=&quot;https://docs.taskcluster.net/docs/tutorial/local-dev&quot;&gt;tutorial: how to run tc locally&lt;/a&gt;. Improving search functionality of the documentation, along with providing client library code examples are in my future plans.&lt;/p&gt;

&lt;p&gt;I planned to release a TUI utility to manage Taskcluster deployments. Something like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k9s&lt;/code&gt; but for taskcluster. Being able to navigate through worker pools, workers, scopes, roles, tasks. This could be a fun tool, I just need to find more time to continue working on it.&lt;/p&gt;

&lt;p&gt;Few times I was trying to fix something without understanding or seeing the bigger picture, which lead to increased complexity of the system. Luckily I have great team members who are willing to tell me about the alternatives.&lt;/p&gt;

&lt;p&gt;Taskcluster’s codebase is quite old, and was created at times where ESM modules didn’t exist yet. Now, more and more libraries are switching to ESM-only packages, and it is impossible to upgrade those. This is something that we’ll need to look into and make necessary changes to the monorepo to add support to ESM modules.&lt;/p&gt;

&lt;p&gt;I’ve also experimented with adding Typescript support to it, but didn’t have time to finish those experiments.&lt;/p&gt;

&lt;p&gt;Another big issue is that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ui&lt;/code&gt; uses &lt;a href=&quot;https://neutrinojs.org/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;neutrinojs&lt;/code&gt;&lt;/a&gt; that was created by great folks in Mozilla some time ago, but, unfortunately, it is no longer actively maintained. And this prevents us from doing some upgrades and changes. It is also based on older versions of webpack which are very slow in making builds and in local development. It is especially painful to build static components inside docker. The plan is here to migrate to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vite&lt;/code&gt; and get rid of neutrino, but it wasn’t finished yet. &lt;a href=&quot;https://github.com/taskcluster/taskcluster/pull/5610&quot;&gt;WIP PR&lt;/a&gt; open since August’22.&lt;/p&gt;

&lt;p&gt;And of course OKRs. I have a privilege on this project to decide myself what to work on, to set my own goals. Some OKRs where a hit, some were a miss. As always, it is better to under-promise and over-deliver.&lt;/p&gt;

&lt;p&gt;And also I’ve broken few builds, which blocked few teams and made few releases roll back.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Stay tuned :)&lt;/p&gt;
</description>
        <pubDate>Sun, 15 Jan 2023 00:00:00 +0000</pubDate>
        <link>http://yaraslav.com//2023/01/15/one-year-at-mozilla.html</link>
        <guid isPermaLink="true">http://yaraslav.com//2023/01/15/one-year-at-mozilla.html</guid>
        
        <category>mozilla</category>
        
        <category>taskcluster</category>
        
        
      </item>
    
      <item>
        <title>Docker compose profiles</title>
        <description>&lt;h2 id=&quot;so-you-have-too-many-micro--services&quot;&gt;So you have too many (micro-) services..&lt;/h2&gt;

&lt;p&gt;It is quite common to have &lt;a href=&quot;https://docs.docker.com/compose/&quot;&gt;docker compose&lt;/a&gt; for your local development. Sometimes even for production. But as your project grows you have more and more services and containers. It takes a lot of resources to run them at once.&lt;/p&gt;

&lt;p&gt;To prevent docker compose from starting all services, you can use &lt;a href=&quot;https://docs.docker.com/compose/profiles/&quot;&gt;service profiles&lt;/a&gt; for this.&lt;/p&gt;

&lt;p&gt;Official &lt;a href=&quot;https://docs.docker.com/compose/profiles/&quot;&gt;documentation&lt;/a&gt; contains some examples, but I will provide another real-world example below.&lt;/p&gt;

&lt;h2 id=&quot;yet-another-web-app-example&quot;&gt;Yet another web app example&lt;/h2&gt;

&lt;p&gt;Let’s say our application consists of the frontend application, backend API, database, two background workers and one cronjob:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&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;postgres&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;frontend&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&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;your-app/frontend&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;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;your-app/backend&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;s&quot;&gt;api&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;backend-worker-1&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&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;your-app/backend&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;s&quot;&gt;worker-1&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;backend-worker-2&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&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;your-app/backend&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;s&quot;&gt;worker-2&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;cronjob&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&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;your-app/backend&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;s&quot;&gt;cronjob&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So if you run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker compose up -d&lt;/code&gt; now, it will start all 6 services at once. Quite possible, you only wanted to start backend and frontend.&lt;/p&gt;

&lt;p&gt;You can introduce profiles for the services that don’t need to start:&lt;/p&gt;

&lt;div class=&quot;language-patch highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  backend-worker-1:
    image: your-app/backend
    command: worker-1
&lt;span class=&quot;gi&quot;&gt;++  profiles: [&quot;backend&quot;, &quot;worker&quot;]
&lt;/span&gt;
  backend-worker-2:
    image: your-app/backend
    command: worker-2
&lt;span class=&quot;gi&quot;&gt;++  profiles: [&quot;backend&quot;, &quot;worker&quot;]
&lt;/span&gt;
  cronjob:
    image: your-app/backend
    command: cronjob
&lt;span class=&quot;gi&quot;&gt;++  profiles: [&quot;backend&quot;, &quot;cron&quot;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With those changes when you run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker compose up -d&lt;/code&gt;, those extra services will no longer start. Only &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;db&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;frontend&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;backend&lt;/code&gt; will.&lt;/p&gt;

&lt;p&gt;If you want to run those services, you have three options:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker compose up -d backend-worker-2&lt;/code&gt; this will auto-enable profile and start specific service&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker compose --profile worker up -d&lt;/code&gt; will start both workers, as both of them have this profile. Keep in mind, profile argument is positional, you cannot use it after &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;up&lt;/code&gt; command.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;COMPOSE_PROFILES=backend docker compose up -d&lt;/code&gt; will start all backend workers and cronjob using env var.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I’ve noticed that second approach is quite tedious. You need to remember which profile you used. This will be a problem once you try to stop all containers with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker compose down&lt;/code&gt; and you will notice that those extra workers didn’t stop.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Note: if you use networks, you might additionally see this error: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;failed to remove network network_name: Error response from daemon: error while removing network: network network_name id f3f6e7fc5 has active endpoints&lt;/code&gt;.&lt;/p&gt;

  &lt;p&gt;This is because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;down&lt;/code&gt; command didn’t know about those extra profiles. To solve this you need to call it with exactly same profile names: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker compose --profile worker down&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And with environmental variable you can do:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;COMPOSE_PROFILES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;backend
docker compose up &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# will start all&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# ...&lt;/span&gt;
docker compose down  &lt;span class=&quot;c&quot;&gt;# this will stop all according to COMPOSE_PROFILES&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Of course, this only works in the same terminal session, or if you have this variable set globally.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;We started using profiles in my current project - &lt;a href=&quot;https://github.com/taskcluster/taskcluster&quot;&gt;Taskcluster&lt;/a&gt; because of the big amount of services.&lt;/p&gt;

&lt;p&gt;Currently Taskcluster’s &lt;a href=&quot;https://github.com/taskcluster/taskcluster/blob/main/docker-compose.yml&quot;&gt;docker-compose.yml&lt;/a&gt; defines more than 50 services, most of which are periodic cronjobs or background workers that are not needed during local development.&lt;/p&gt;
</description>
        <pubDate>Sun, 14 Aug 2022 00:00:00 +0000</pubDate>
        <link>http://yaraslav.com//2022/08/14/docker-compose-profiles.html</link>
        <guid isPermaLink="true">http://yaraslav.com//2022/08/14/docker-compose-profiles.html</guid>
        
        <category>docker</category>
        
        <category>compose</category>
        
        
      </item>
    
      <item>
        <title>First month at mozilla</title>
        <description>&lt;p&gt;It’s been one month since I’ve joined Mozilla as a software engineer. I am part of the system engineering team that handles &lt;a href=&quot;https://taskcluster.net/&quot;&gt;taskcluster&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Taskcluster is crucial for Mozilla projects. It orchestrates thousands of builds, tests and release tasks daily. It deserves a separate post, which I will do next.&lt;/p&gt;

&lt;p&gt;It took me exactly 1 month to &lt;a href=&quot;https://github.com/taskcluster/taskcluster/pull/5163&quot;&gt;contribute&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are things that are new to me, and I would like to share some of those:&lt;/p&gt;

&lt;h3 id=&quot;many-people-work-in-mozilla-long-years&quot;&gt;Many people work in Mozilla long years&lt;/h3&gt;

&lt;p&gt;5-7 years is the usual number. Quite often you meet people that have worked 10+ years. But there are those who have worked since the beginning.&lt;/p&gt;

&lt;p&gt;It would be good for me to find out what motivates them ;)&lt;/p&gt;

&lt;h3 id=&quot;working-on-open-sourced-projects-makes-lots-of-people-open-too&quot;&gt;Working on open-sourced projects makes lots of people open, too&lt;/h3&gt;

&lt;p&gt;They discuss things they are &lt;a href=&quot;http://www.chesnok.com/daily/2016/03/11/workweek-tc-worker-workweek-recap/&quot;&gt;working&lt;/a&gt; on and &lt;a href=&quot;https://hassanali.me/2018/05/18/rethinking-our-ui.html&quot;&gt;problems they have&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I admire how &lt;a href=&quot;https://factorialfive.org/2022/01/22/one-more-week-designing-the-next-taskcluster-monitoring-system/&quot;&gt;John Whitlock&lt;/a&gt; keeps his dairy public where he describes all the things he’s working on.&lt;/p&gt;

&lt;p&gt;Would be great to follow the same principles and share my progress, as well.&lt;/p&gt;

&lt;h3 id=&quot;importance-to-self-organize&quot;&gt;Importance to self-organize&lt;/h3&gt;

&lt;p&gt;Being remote demands you are able to organize yourself:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;create your own schedule and stick to it. Free to work evenings/mornings/combination of both with long pause in the afternoon&lt;/li&gt;
  &lt;li&gt;keep detailed log of all activities (meetings, tasks you are working on, things you learned, etc)&lt;/li&gt;
  &lt;li&gt;make your own notes of every meeting. It helps to revisit them, so you have a better understanding and make sure you don’t miss something.&lt;/li&gt;
  &lt;li&gt;write down people’s names, what they are doing and how you can help each other. There are way too many people to remember everything&lt;/li&gt;
  &lt;li&gt;write plans/goals for the next day, next week, next month, …&lt;/li&gt;
  &lt;li&gt;dump all your ideas at the end of the day, things that you didn’t finish, or would like to work on, and use it a plan next day&lt;/li&gt;
  &lt;li&gt;keep a list of all the things you discover, everything that you can improve,  fix, change or remove. Revisit and check if you can execute on those&lt;/li&gt;
  &lt;li&gt;review past meeting notes to spot forgotten action items&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;time-is-relative&quot;&gt;Time is relative&lt;/h3&gt;

&lt;p&gt;If you try to schedule one on one with people, some will be happy to talk to you tomorrow, some next week, some next month. Urgency is usually not a thing compared to the startup world.&lt;/p&gt;

&lt;h3 id=&quot;community-involvement&quot;&gt;Community involvement&lt;/h3&gt;

&lt;p&gt;For Mozilla the community is the integral part of their being. In my Taskcluster project we have weekly community meetings. It gives an excellent opportunity to talk to people outside of Mozilla who use the same project. Give them status updates on the progress. Take feedback and listen to their opinions.&lt;/p&gt;

&lt;p&gt;Those meetings happen even if there is only one person joining. Which is great!&lt;/p&gt;

&lt;h3 id=&quot;mozilla-originals&quot;&gt;Mozilla originals&lt;/h3&gt;

&lt;p&gt;Turns out there are many wonderful projects that are being developed or originated at Mozilla:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.bugzilla.org/&quot;&gt;bugzilla&lt;/a&gt; .. as old as Firefox or even older. Fun fact, JIRA is coming from Gojira, Japanese for Godzilla.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/mozilla/sops&quot;&gt;sops&lt;/a&gt; modern way of managing secrets which can check-in to vcs&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://letsencrypt.org/&quot;&gt;letsencrypt&lt;/a&gt; standard nowadays for HTTPS certificates&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://json-e.js.org/&quot;&gt;json-e&lt;/a&gt; extended json to convert &lt;em&gt;boring yaml&lt;/em&gt; into &lt;em&gt;dynamic yaml&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ll keep discovering and extending this list.&lt;/p&gt;

&lt;h3 id=&quot;contribute---codetribute&quot;&gt;Contribute - Codetribute&lt;/h3&gt;

&lt;p&gt;If you feel like contributing but don’t know where to start, there is a great page: &lt;a href=&quot;https://codetribute.mozilla.org/&quot;&gt;Codetribute&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can choose your preferred language, pick a bug/task and improve something.&lt;/p&gt;
</description>
        <pubDate>Mon, 14 Feb 2022 00:00:00 +0000</pubDate>
        <link>http://yaraslav.com//2022/02/14/one-month-at-mozilla.html</link>
        <guid isPermaLink="true">http://yaraslav.com//2022/02/14/one-month-at-mozilla.html</guid>
        
        <category>mozilla</category>
        
        
      </item>
    
      <item>
        <title>Docker Desktop alternatives for Mac with M1 chips</title>
        <description>&lt;p&gt;Docker Desktop is not the only way to run Docker on Mac with M1 chips (as of November 2021, updated July 2023).&lt;/p&gt;

&lt;p&gt;Important thing to know and understand is that your docker images must have either aarch64 (arm64) or x86 (amd64) architecture.
Afaik, you can mix both to some extent, but only with Docker Desktop.&lt;/p&gt;

&lt;p&gt;I experimented with alternatives, and they are quite nice to work with, but not everyone knows about them.&lt;/p&gt;

&lt;h2 id=&quot;orbstack&quot;&gt;&lt;a href=&quot;https://orbstack.dev/&quot;&gt;OrbStack&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;I’ve learned about &lt;a href=&quot;https://orbstack.dev/&quot;&gt;OrbStack&lt;/a&gt; in June 2023. It is a &lt;a href=&quot;https://docs.orbstack.dev/architecture&quot;&gt;similar to WSL2&lt;/a&gt; approach to run lightweight Linux virtual machine on M1 mac.
It integrates itself into command line and gives you both &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker&lt;/code&gt; support and ability to run linux virtual machine.&lt;/p&gt;

&lt;p&gt;You can use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;orb&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;orbctl&lt;/code&gt; to manage VMs, or use docker right away.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; 80:80 docker/getting-started
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;UI itself is minimalistic, yet powerful and extremely fast. It can ran 20+ &lt;a href=&quot;https://github.com/taskcluster/taskcluster&quot;&gt;taskcluster&lt;/a&gt; containers easily, and CPU usage is minimal, comparing to Docker Desktop.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;As of 2023 OrbStack is my favourite choice on M1/M2 Macs due to it’s low footprint and speed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;multipass&quot;&gt;Multipass&lt;/h2&gt;

&lt;p&gt;Easiest way to launch Ubuntu VMs is by using &lt;a href=&quot;https://multipass.run/&quot;&gt;multipass&lt;/a&gt; from Canonicial.&lt;/p&gt;

&lt;p&gt;Installation is trivial: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;brew install --cask multipass&lt;/code&gt;. After this you can create and run as many VMs as you want, provided you have enough CPU and memory.&lt;/p&gt;

&lt;p&gt;List available VMs:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;multipass &lt;span class=&quot;nb&quot;&gt;ls

&lt;/span&gt;Name                    State             IPv4             Image
primary                 Stopped           &lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt;               Ubuntu 20.04 LTS
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Primary should be installed by default, if not one can run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;multipass launch --name primary --cpus 2 --mem 4G --disk 20G&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you see something like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Launch failed: Operation canceled&lt;/code&gt; you can run according to &lt;a href=&quot;https://github.com/canonical/multipass/issues/2288#issuecomment-963583241&quot;&gt;this comment&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;launchctl stop com.canonical.multipassd
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;launchctl start com.canonical.multipassd
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Controlling it’s state is as easy as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;multipass start&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;multipass stop&lt;/code&gt;, to enter &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;multipass shell&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Cool thing about it, is that can be used as a default linux environment for development, not just for Docker.&lt;/p&gt;

&lt;p&gt;It mounts automatically your home folder to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/home/ubuntu/Home&lt;/code&gt; using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sshfs&lt;/code&gt;, so you can run commands inside, but edit files on the host machine.&lt;/p&gt;

&lt;p&gt;To get docker up and running:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt update
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; docker.io docker-compose
&lt;span class=&quot;c&quot;&gt;# to be able to run docker without sudo&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;usermod &lt;span class=&quot;nt&quot;&gt;-aG&lt;/span&gt; docker &lt;span class=&quot;nv&quot;&gt;$USER&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After that you can go and run your containers as you did so far. If you want to do it from the host machine, you’d need to set up &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker context&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On your host machine you should have &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker&lt;/code&gt; binary either from Docker Desktop (which you don’t need to start), from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;brew&lt;/code&gt;, or other source. Docker can fire commands against docker daemon that runs inside ubuntu machine. To do so, it needs ssh access inside the machine, it will need &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ssh ubuntu@IP&lt;/code&gt; (IP can be seen in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;multipass ls&lt;/code&gt; output).&lt;/p&gt;

&lt;p&gt;Make sure to add your ssh key to the ubuntu machine, so you can access it from the host machine.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# on host&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; ~/.ssh/id_rsa.pub | pbcopy

&lt;span class=&quot;c&quot;&gt;# inside ubuntu vm&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&amp;lt;your_key&amp;gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.ssh/authorized_keys
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With that done, you can add docker context on your host machine&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Create context&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker context create multipass &lt;span class=&quot;nt&quot;&gt;--docker&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;host=ssh://ubuntu@192.168.64.7&quot;&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Switch to it&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker context use multipass
&lt;span class=&quot;c&quot;&gt;# Run container&lt;/span&gt;
&lt;span class=&quot;nv&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;-it&lt;/span&gt; alpine /bin/sh

&lt;span class=&quot;c&quot;&gt;# In case previous command fails with&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# `exec user process caused: exec format error`, you&apos;d need to specify platform:&lt;/span&gt;
&lt;span class=&quot;nv&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;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--platform&lt;/span&gt; linux/arm64/8 alpine /bin/sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As far as I understand, ports are not forwarded automatically, so you need to forward them manually, or just use vm IP instead of localhost, i.e: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://192.168.64.7:5000&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;multipass start
multipass start  0.03s user 0.03s system 0% cpu 19.318 total

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;mp stop
multipass stop  0.02s user 0.02s system 3% cpu 1.219 total
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;lima&quot;&gt;&lt;a href=&quot;https://github.com/lima-vm/lima&quot;&gt;Lima&lt;/a&gt;&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;Lima launches Linux virtual machines with automatic file sharing, port forwarding, and containerd.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fast and lightweight, you are not limited to Ubuntu. Can be installed with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;brew install lima&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Similar workflow - create one or more VMs, and then run containers inside them, or just use them as a default linux environment.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# launch linux&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;limactl start

&lt;span class=&quot;c&quot;&gt;# shell into&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;lima
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will open an editor to specify the configuration for the new VM, like architecture, mounts, ports, etc.&lt;/p&gt;

&lt;p&gt;There are plenty of &lt;a href=&quot;https://github.com/lima-vm/lima/tree/master/examples&quot;&gt;examples&lt;/a&gt; how to configure for different use-cases (k3s, k8s, alpine, centos, etc)&lt;/p&gt;

&lt;p&gt;Can run x86 with qemu emulation, but is rather slow. While aarch64 feels like native.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;limactl start

limactl start  0.02s user 0.03s system 0% cpu 22.145 total

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;limactl stop

limactl stop  0.01s user 0.02s system 3% cpu 1.069 total
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;podman-machine&quot;&gt;&lt;a href=&quot;https://docs.podman.io/en/latest/markdown/podman-machine.1.html&quot;&gt;Podman machine&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.podman.io/en/latest/index.html&quot;&gt;Podman&lt;/a&gt; is a daemonless container runtime for Linux.&lt;/p&gt;

&lt;p&gt;It claims to be a drop-in replacement for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker&lt;/code&gt;, but on Mac you obviously need an underlying linux VM running.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Podman machine&lt;/strong&gt; is a tool to create and manage VMs for Podman. (This was introduced recently, as a replacement for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;podman-machine&lt;/code&gt;).
Naming, is hard… remember.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;podman machine init
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;podman machine start
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;podman machine ssh &lt;span class=&quot;c&quot;&gt;# but you don&apos;t have to, you can stay on host and run `podman` as `docker&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;One thing to note is that you don’t have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose&lt;/code&gt; equivalent, althought there is such thing as &lt;a href=&quot;https://github.com/containers/podman-compose&quot;&gt;podman-compose&lt;/a&gt;. I believe this is because podman is trying to be closer to kubernetes and provides k8s compatible configs and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pods&lt;/code&gt;.
You can create &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pod&lt;/code&gt;, and attach multiple containers to it, which will share fs, ports, etc. Pretty much as pod in k8s.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;podman machine start

podman machine start  0.02s user 0.03s system 0% cpu 14.526 total

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;podman machine stop
&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;podman machine

podman machine stop  0.01s user 0.02s system 27% cpu 0.110 total
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;docker-desktop&quot;&gt;&lt;a href=&quot;https://www.docker.com/products/docker-desktop&quot;&gt;Docker desktop&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;Yep, rather slow, compared to lima and multipass, but can run both x86 and aarch64 images simultaneously.&lt;/p&gt;

&lt;p&gt;Upd 2023: Seems like Docker Desktop was seriously improved and provides significantly better performance,
also UI gives much more options now for monitoring, viewing files and seamlessly executing in containers.&lt;/p&gt;

&lt;h2 id=&quot;utm&quot;&gt;&lt;a href=&quot;https://mac.getutm.app/&quot;&gt;UTM&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;This is a GUI tool to run VM’s on Mach. Not limited to linux, can run Windows as well.&lt;/p&gt;

&lt;p&gt;You can launch favourite linux distro.&lt;/p&gt;

&lt;p&gt;Personaly, I don’t consider this as a good alternative, as you it provides less integrations with docker and command line.&lt;/p&gt;

</description>
        <pubDate>Mon, 01 Nov 2021 00:00:00 +0000</pubDate>
        <link>http://yaraslav.com//2021/11/01/docker-mac-m1.html</link>
        <guid isPermaLink="true">http://yaraslav.com//2021/11/01/docker-mac-m1.html</guid>
        
        <category>docker</category>
        
        <category>mac</category>
        
        <category>arm</category>
        
        <category>m1</category>
        
        
      </item>
    
      <item>
        <title>My done is done done</title>
        <description>&lt;p&gt;It is difficult to imagine an IT company nowadays that doesn’t speak scrums.
Every job description you read, every company you talk to will tell you that they are doing “sprints” and that they are “mostly agile”.&lt;/p&gt;

&lt;p&gt;And unless you work alone or in a very small group, chances are you have a lot of participants. Each feature will go through the hands of a bunch of people during its lifetime. Let’s take this example:&lt;/p&gt;

&lt;h4 id=&quot;the-flow&quot;&gt;The flow&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Stakeholders&lt;/strong&gt; or business will come up with feature idea&lt;br /&gt;
&lt;strong&gt;Product owner&lt;/strong&gt; or &lt;strong&gt;project manager&lt;/strong&gt; will gather all requirements and will come up with development/release plan&lt;br /&gt;
&lt;strong&gt;UX/UI expert&lt;/strong&gt; will do analysis and initial design based on the requirements they have&lt;br /&gt;
&lt;strong&gt;Developers&lt;/strong&gt; will take the requirements, prototype, design and will work on a feature, deploy it to a staging environment&lt;br /&gt;
&lt;strong&gt;Different developers&lt;/strong&gt; would normally do a code review of the introduced changes&lt;br /&gt;
&lt;strong&gt;QA&lt;/strong&gt; will check if feature works according to initial requirements and UX rules&lt;br /&gt;
&lt;strong&gt;Developers&lt;/strong&gt; will fix the bugs would there be any&lt;br /&gt;
&lt;strong&gt;Product owner&lt;/strong&gt; together with &lt;strong&gt;stakeholders&lt;/strong&gt; will do their &lt;strong&gt;business acceptance&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;Someone&lt;/strong&gt; (developer, release manager, lead dev) will release this feature to prod&lt;br /&gt;
Plus ideally providing all features with full test coverage - unit/integration/e2e tests&lt;/p&gt;

&lt;p&gt;This is a more or less happy flow for small features that don’t have many dependencies and don’t need to be broken down into smaller features. This is also a scenario where &lt;strong&gt;developers&lt;/strong&gt; are cross-functional and can do everything (backend/frontend/devops) without making this process even more complicated. Developing complex features usually takes more teams, more coordination between those teams and more communication. I will leave it out for now.&lt;/p&gt;

&lt;h4 id=&quot;my-done-is-done-done&quot;&gt;My done is done done&lt;/h4&gt;

&lt;p&gt;Now if you ask participants, what they think when they are &lt;strong&gt;done&lt;/strong&gt;, you would probably hear many different things:&lt;br /&gt;
&lt;em&gt;“I finished working on mockups and design, I’m &lt;strong&gt;done&lt;/strong&gt;“&lt;/em&gt;&lt;br /&gt;
&lt;em&gt;“I’m &lt;strong&gt;done&lt;/strong&gt; with development of this feature”&lt;/em&gt;,&lt;br /&gt;
&lt;em&gt;“I’m &lt;strong&gt;done&lt;/strong&gt; testing, sent it back to developers”&lt;/em&gt;,&lt;br /&gt;
&lt;em&gt;“I’m &lt;strong&gt;done&lt;/strong&gt; reviewing the ticket”&lt;/em&gt;,&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;And it is kinda normal nowadays. Projects are big, there are many features to work on, constant time pressure, etc, etc. Often it comes down to &lt;strong&gt;fire and forget&lt;/strong&gt; scenarios.&lt;/p&gt;

&lt;h4 id=&quot;problems&quot;&gt;Problems&lt;/h4&gt;

&lt;p&gt;This kind of attitude often results in:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;too many features are developed at the same time (different participants)&lt;/li&gt;
  &lt;li&gt;features rarily meet deadlines&lt;/li&gt;
  &lt;li&gt;tickets are being underestimated&lt;/li&gt;
  &lt;li&gt;lack of understanding the scope of the ticket&lt;/li&gt;
  &lt;li&gt;missed sprint goals&lt;/li&gt;
  &lt;li&gt;slow development/release pace&lt;/li&gt;
  &lt;li&gt;forgotten/abandoned merge requests or PRs that stay “forever” in open state&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;definition-of-done&quot;&gt;Definition of done&lt;/h4&gt;

&lt;p&gt;For most of the features definition of done will look like the &lt;strong&gt;flow&lt;/strong&gt; above. The final &lt;strong&gt;done&lt;/strong&gt; would be a release to prod and closing of the ticket.&lt;/p&gt;

&lt;p&gt;Each team member should:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;have a clear understanding of what &lt;strong&gt;done&lt;/strong&gt; is&lt;/li&gt;
  &lt;li&gt;be responsible for the success of the whole team&lt;/li&gt;
  &lt;li&gt;be able to follow feature through all steps&lt;/li&gt;
  &lt;li&gt;be able to communicate problems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of course, different members may join at different stages of feature development lifecycle. But it is still important to communicate goals, deadlines and requirements well enough that everybody could understand what their role is.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;ideal&lt;/strong&gt; flow no one will say &lt;em&gt;“it’s done”&lt;/em&gt; until feature is released and ticket is closed.&lt;/p&gt;
</description>
        <pubDate>Thu, 10 Jun 2021 00:00:00 +0000</pubDate>
        <link>http://yaraslav.com//2021/06/10/when-done-is-done.html</link>
        <guid isPermaLink="true">http://yaraslav.com//2021/06/10/when-done-is-done.html</guid>
        
        <category>scrum</category>
        
        <category>definition of done</category>
        
        <category>development</category>
        
        <category>thoughts</category>
        
        <category>it</category>
        
        
      </item>
    
      <item>
        <title>Docker called me</title>
        <description>&lt;p&gt;Story about me giving feedback to Docker.&lt;/p&gt;

&lt;h2 id=&quot;docker&quot;&gt;Docker&lt;/h2&gt;

&lt;p&gt;I’ve been using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker&lt;/code&gt; since 2014. It is hard to imagine nowadays development and deployment without the containers.&lt;/p&gt;

&lt;p&gt;Using it on non-linux machines, especially on MacOS was always somewhat problematic. Running out of disk space, fans spinning like crazy, networking peculiarities.
Evolution of tooling and introduction of Docker Desktop improved things a lot. No more self-managed virtual machines, no more Vagrant provisioning. Start the desktop app and start using docker, magic.&lt;/p&gt;

&lt;h2 id=&quot;docker-developer-preview-program&quot;&gt;Docker Developer Preview Program&lt;/h2&gt;

&lt;p&gt;This year I gave in and finally bought my first personal laptop since 2010. Because I was using laptops from work for my personal stuff from time to time. But the magic of M1 silicon was too attractive to resist it.&lt;/p&gt;

&lt;p&gt;With M1 came new problems, especially with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x86&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;arm64&lt;/code&gt; images and ability to run Docker at all. But it didn’t take long to roll out the first public betas for Developer Preview, so I decided to join this program.&lt;/p&gt;

&lt;p&gt;I wanted to get new releases faster and  communicate M1 specific issues.
So I joined a &lt;a href=&quot;dockercommunity.slack.com&quot;&gt;Docker Community Slack&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There is a small form to enroll into &lt;a href=&quot;https://www.docker.com/community/get-involved/developer-preview&quot;&gt;Docker Developer Preview Program&lt;/a&gt;.
One of the questions is “What one feature would you add to Desktop?”&lt;/p&gt;

&lt;p&gt;I realised that it would be nice to add Volume management and disk space management into the desktop app.&lt;/p&gt;

&lt;h2 id=&quot;the-call&quot;&gt;The call&lt;/h2&gt;

&lt;p&gt;Few months later I received an email from &lt;a href=&quot;https://www.linkedin.com/in/dieucao/&quot;&gt;Dieu Cao&lt;/a&gt; who is a Senior Product Management Director at Docker, Inc.
She is interested in discussing the Volume Management I’ve written about in a feedback form.&lt;/p&gt;

&lt;p&gt;This was a nice surprise and the first time any software vendor of this size came back to me. It feels good, when someone is interested in hearing feedback from you.&lt;/p&gt;

&lt;p&gt;Zoom call consisted of several participants. Besides Dieu, I’ve got a chance to speak to &lt;a href=&quot;https://www.linkedin.com/in/djordjelukic/&quot;&gt;Djordje Lukic&lt;/a&gt; (Staff Software Engineer), &lt;a href=&quot;https://www.linkedin.com/in/ushamandya/&quot;&gt;Usha Mandya&lt;/a&gt; (Senior Technical Writer) and &lt;a href=&quot;https://www.linkedin.com/in/trungutt/&quot;&gt;Trung Nguyen&lt;/a&gt; (Software Engineer).&lt;/p&gt;

&lt;p&gt;Although I didn’t have much time to prepare for the call, I was able to describe my thoughts and pain points we have using Docker Desktop.
I mentioned that one of the bigger issues on MacOS is that disk space tends to disappear quite fast.
Not all IT staff in my company are tech-savvy and know how to fix issues. Like executing several CLI commands to free up space:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# analyse disk usage&lt;/span&gt;
docker system &lt;span class=&quot;nb&quot;&gt;df&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# clean what can be cleaned&lt;/span&gt;
docker system prune
&lt;span class=&quot;c&quot;&gt;# drop unused containers&lt;/span&gt;
docker ps &lt;span class=&quot;nt&quot;&gt;-a&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep &lt;/span&gt;Exited | &lt;span class=&quot;nb&quot;&gt;cut&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;\&apos;&apos; &apos;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\\&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;&apos;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; 1 | xargs docker &lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# then a bit more with images&lt;/span&gt;
docker rmi &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;docker images &lt;span class=&quot;nt&quot;&gt;--filter&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dangling=true&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-q&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--no-trunc&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# and then even more&lt;/span&gt;
docker volume prune
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And while you already can see images and containers in Desktop App, you can not do the same with Volumes. And volumes tend to grow in size, and in case of our application, we have a lot of those. So disk space ends fast.
Having this feature inside the app would make it much easier to manage and free-up space when it’s needed.&lt;/p&gt;

&lt;p&gt;I also mentioned that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compose&lt;/code&gt; integration can be improved.
Our application consists of 25 services. Orchestrating them in the Desktop App is not always an easy task.
Sometimes you get &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cannot execute docker-compose script&lt;/code&gt; . Sometimes some Environment variables are missing or mounts are empty.&lt;/p&gt;

&lt;p&gt;Dieu said that they’ve been working on Volume Management already. And they wanted to share it with me already to hear my feedback.
How awesome! This feature is due to be released next week, and I have an pleasure to review it and give feedback 😏&lt;/p&gt;

&lt;p&gt;So I’ve got a chance to sneak peak new features in action, leave my comments and also learn something new for myself.
They described some extra functionality related to volumes that I’d never thought about before.&lt;/p&gt;

&lt;p&gt;That was a surprisingly great experience for me, and I hope my feedback was helpful to guys and girls from Docker.&lt;/p&gt;

&lt;h2 id=&quot;takeaways&quot;&gt;Takeaways&lt;/h2&gt;

&lt;p&gt;Very positive experience and solid approach from the Docker team. Reaching out to the primary audience to discuss the major pain points is the right attitude.&lt;/p&gt;

&lt;p&gt;I wish more companies would be that open and willing to accept ideas and suggestions.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/docker/zoom.png&quot; alt=&quot;Zoom call&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/docker/dev-preview-program.png&quot; alt=&quot;Dev preview program&quot; /&gt;&lt;/p&gt;
</description>
        <pubDate>Thu, 03 Jun 2021 00:00:00 +0000</pubDate>
        <link>http://yaraslav.com//2021/06/03/docker-called-me.html</link>
        <guid isPermaLink="true">http://yaraslav.com//2021/06/03/docker-called-me.html</guid>
        
        <category>docker</category>
        
        <category>feedback</category>
        
        <category>docker desktop</category>
        
        <category>macos</category>
        
        
      </item>
    
      <item>
        <title>Controlling Phillips HUE lights with Arduino</title>
        <description>&lt;p&gt;Home improvement project with Arduino and HUE lights.&lt;/p&gt;

&lt;h1 id=&quot;controlling-phillips-hue-lights-with-arduino&quot;&gt;Controlling Phillips HUE lights with Arduino&lt;/h1&gt;

&lt;p&gt;I have a bunch of HUE lamps at home that are mainly controlled from the app.&lt;/p&gt;

&lt;p&gt;Playing with Arduino, I thought that I can easily add some motion detection and remote control for some of the lamps.&lt;/p&gt;

&lt;h2 id=&quot;hardware&quot;&gt;Hardware&lt;/h2&gt;

&lt;p&gt;What we have is:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://create.arduino.cc/projecthub/products/arduino-nano-33-iot&quot;&gt;Arduino Nano 33 IoT&lt;/a&gt; which comes with WiFi and Bluetooth&lt;/li&gt;
  &lt;li&gt;Infrared Motion Sensor (HC-SR501)&lt;/li&gt;
  &lt;li&gt;Phillips HUE Bridge API integration&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;hue-bridge-api&quot;&gt;Hue Bridge API&lt;/h2&gt;

&lt;p&gt;One has to &lt;a href=&quot;https://developers.meethue.com/develop/get-started-2/&quot;&gt;obtain new access&lt;/a&gt; token in order to have access to the lights API.&lt;/p&gt;

&lt;p&gt;Once the key is obtained, lights can be turned on and off by doing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PUT&lt;/code&gt; requests to the bridge:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; PUT &lt;span class=&quot;s1&quot;&gt;&apos;http://YOUR-BRIDGE-IP/api/YOUR-API-TOKEN/lights/3/state&apos;&lt;/span&gt;  &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;{&quot;on&quot;: true, &quot;sat&quot;: 54, &quot;bri&quot;: 130 ,&quot;hue&quot;: 33}&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It is possible to change color, brightness and on/off state. For our scenario, we are only interested in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{&quot;on&quot;: true}&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{&quot;on&quot;: false}&lt;/code&gt;&lt;/p&gt;

&lt;h2 id=&quot;assembly&quot;&gt;Assembly&lt;/h2&gt;

&lt;p&gt;PIR sensor has 3 connections:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;VCC -&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+5V&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;GND -&amp;gt; GND&lt;/li&gt;
  &lt;li&gt;OUT -&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2&lt;/code&gt; (2nd pin in my case)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note about &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+5V&lt;/code&gt; which was required by sensor: Arduino Nano 33 IoT have &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+5V&lt;/code&gt; disabled by default (!).
And it can only be operated if powered by USB.
In order to get this &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+5V&lt;/code&gt; on the pin, one need to solder (connect) two &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VUSB&lt;/code&gt; connectors next to it.&lt;/p&gt;

&lt;h2 id=&quot;code&quot;&gt;Code&lt;/h2&gt;

&lt;p&gt;Setup phase consists of setting up pins and connecting to WiFi.&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#define SENSOR_PIN 2
#define SECRET_SSID &quot;home-ssid&quot;
#define SECRET_PASS &quot;home-pass&quot;
&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WL_IDLE_STATUS&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;ssid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SECRET_SSID&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;pass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SECRET_PASS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sensorValue&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;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;oldValue&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;n&quot;&gt;WiFiClient&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&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;nf&quot;&gt;setup&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;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;9600&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// When debugging locally following line can be uncommented, so we can see whole output from the device&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// while (!Serial);&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Setup pins&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;pinMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SENSOR_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;INPUT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;pinMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LED_BUILTIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OUTPUT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// check for the WiFi module:&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;WiFi&lt;/span&gt;&lt;span class=&quot;p&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;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WL_NO_MODULE&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;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Communication with WiFi module failed!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// don&apos;t continue&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;nb&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;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fv&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WiFi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;firmwareVersion&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;fv&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WIFI_FIRMWARE_LATEST_VERSION&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;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Please upgrade the firmware&quot;&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;// attempt to connect to Wifi network:&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;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WL_CONNECTED&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;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Attempting to connect to SSID: &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ssid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WiFi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ssid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// wait 10 seconds for connection:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5000&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;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Connected to wifi&quot;&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;/div&gt;&lt;/div&gt;

&lt;p&gt;Once connected, we can listen for the sensor value to change:&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loop&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;sensorValue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;digitalRead&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SENSOR_PIN&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;sensorValue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HIGH&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;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;oldValue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LOW&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;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;State changed to: High :)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
     &lt;span class=&quot;n&quot;&gt;digitalWrite&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LED_BUILTIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HIGH&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
     &lt;span class=&quot;n&quot;&gt;sendCommand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&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;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&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;oldValue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HIGH&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;digitalWrite&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LED_BUILTIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LOW&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;State changed to: Low :(&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;sendCommand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;false&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;n&quot;&gt;oldValue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sensorValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&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;c1&quot;&gt;// Not really necessary, but some delay wouldn&apos;t hurt&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Last thing is to actually send a HTTP request to the bridge to turn on/off the lamp:&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;n&quot;&gt;IPAddress&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;192&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;168&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;225&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// IP of the bridge&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;port&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;apiUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;PUT /api/TOKEN/lights/14/state HTTP/1.1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// URL with the lamp id to control&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;sendCommand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;isOn&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;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;{&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;: false}&quot;&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;isOn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nb&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;n&quot;&gt;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Turning lamp on&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;{&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;: true}&quot;&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;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Turning lamp off&quot;&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;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// if previous request is active, it can be stopped&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;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;port&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;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Connected to server&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;apiUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Accept: application/json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Content-Type: application/json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Content-length: &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()));&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Sent: &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;content&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;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Connection failed&quot;&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And that should be it.&lt;/p&gt;

&lt;h2 id=&quot;notes&quot;&gt;Notes&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;PIR sensor turned out to be quite a stubborn one. It has two dials to control sensitivity and delay, and it was tricky to find the proper combination that would work in my room.&lt;/li&gt;
  &lt;li&gt;Code is missing the check for the daylight, as it will make no sense to run it during the day. It can either require additional photo sensor, to see if it is bright enough in order not to switch lamp on.&lt;/li&gt;
  &lt;li&gt;As the next step, It would probably be better to connect to the MQTT and send messages there, because:
    &lt;ul&gt;
      &lt;li&gt;It would make this client simpler, and would save us from talking to the bridge directly.&lt;/li&gt;
      &lt;li&gt;external listeners could use additional information to control the lights&lt;/li&gt;
      &lt;li&gt;it could all be connected to the &lt;a href=&quot;https://www.home-assistant.io/&quot;&gt;Home Assistant&lt;/a&gt; for extra fun, and tracked history of events&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;updated-version-with-mqtt&quot;&gt;Updated version with MQTT&lt;/h2&gt;

&lt;p&gt;To address the issues and shortcomings of direct communication with HUE Bridge, I’ve changed the way how events are being handled. I push those to the MQTT which is running on a home server, driven by Raspberry Pi 4.&lt;/p&gt;

&lt;p&gt;MQTT and &lt;a href=&quot;https://www.home-assistant.io/&quot;&gt;Home Assistant&lt;/a&gt; are running with docker:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&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;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;3&apos;&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;home-assistant&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&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;homeassistant/home-assistant:stable&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;environment&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;TZ=Europe/Berlin&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;s&quot;&gt;8123:8123&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;volumes&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;./config:/config&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;mqtt&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&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;eclipse-mosquitto:1.6&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;s&quot;&gt;1883:1883&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;9001:9001&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Code now looks like this:&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#include&lt;/span&gt; &lt;span class=&quot;cpf&quot;&gt;&amp;lt;SPI.h&amp;gt;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;
#include&lt;/span&gt; &lt;span class=&quot;cpf&quot;&gt;&amp;lt;WiFiNINA.h&amp;gt;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;
#include&lt;/span&gt; &lt;span class=&quot;cpf&quot;&gt;&amp;lt;ArduinoMqttClient.h&amp;gt;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;
&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#define SENSOR_PIN 2
&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#define SECRET_SSID &quot;ssid&quot;
#define SECRET_PASS &quot;pass&quot;
&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ssid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SECRET_SSID&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;pass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SECRET_PASS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;WiFiClient&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;MqttClient&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;mqttClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// sensor values&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sensorValue&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;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;oldValue&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;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;broker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;     &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;192.168.55.55&quot;&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;n&quot;&gt;IP&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;the&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MQTT&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;port&lt;/span&gt;         &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1883&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;topicWrite&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;sensors/event&quot;&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;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sensorId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;   &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;s1&quot;&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;nf&quot;&gt;setup&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;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;9600&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Setup pins&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;pinMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SENSOR_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;INPUT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;pinMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LED_BUILTIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OUTPUT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// check for the WiFi module&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;WiFi&lt;/span&gt;&lt;span class=&quot;p&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;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WL_NO_MODULE&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;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Communication with WiFi module failed!&quot;&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;nb&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;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fv&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WiFi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;firmwareVersion&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;fv&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WIFI_FIRMWARE_LATEST_VERSION&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;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Please upgrade the firmware&quot;&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;// attempt to connect to Wifi network:&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;n&quot;&gt;WiFi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ssid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WL_CONNECTED&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;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Attempting to connect to SSID: &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ssid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5000&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;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Connected to wifi&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;printWifiStatus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Attempting to connect to the MQTT broker: &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;broker&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;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mqttClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;broker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;port&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;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;MQTT connection failed! Error code = &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mqttClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connectError&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;mi&quot;&gt;1&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;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;We are connected to the MQTT broker!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Awaiting orders&quot;&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;nf&quot;&gt;loop&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;// call poll() regularly to allow the library to send MQTT keep alives which&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// avoids being disconnected by the broker&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;mqttClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;poll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;sensorValue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;digitalRead&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SENSOR_PIN&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;sensorValue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HIGH&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;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;oldValue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LOW&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;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;State changed to: High :)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
     &lt;span class=&quot;n&quot;&gt;digitalWrite&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LED_BUILTIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HIGH&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
     &lt;span class=&quot;n&quot;&gt;sendCommand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&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;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&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;oldValue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HIGH&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;digitalWrite&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LED_BUILTIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LOW&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;State changed to: Low :(&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;sendCommand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;false&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;n&quot;&gt;oldValue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sensorValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&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;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;sendCommand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;isOn&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;mqttClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;beginMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;topicWrite&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;isOn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nb&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;n&quot;&gt;mqttClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ON&quot;&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;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;mqttClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;OFF&quot;&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;mqttClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;print&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;n&quot;&gt;mqttClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sensorId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;mqttClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;endMessage&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;nf&quot;&gt;printWifiStatus&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;// print the SSID of the network you&apos;re attached to:&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;SSID: &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WiFi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SSID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// print your board&apos;s IP address:&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;IPAddress&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ip&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WiFi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;localIP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;IP Address: &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// print the received signal strength:&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rssi&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WiFi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&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;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;signal strength (RSSI):&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&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;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot; dBm&quot;&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;/div&gt;&lt;/div&gt;

&lt;p&gt;I’ve chose to publish events to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sensor/event&lt;/code&gt; with two messages &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON|s1&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OFF|s1&lt;/code&gt;. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;s1&lt;/code&gt; is just in case I’d add more later, and plain text for simplicity.
It could have also been a separate queue for on/off.&lt;/p&gt;

&lt;p&gt;Now, when the sensor is only sending messages to the queue, we can do the heavy part on the Home Assistant side.&lt;/p&gt;

&lt;p&gt;For this I used built-in Automation tools, adding two listeners, one that listens to the MQTT message and turns lamp on, and the one that turns lights off.&lt;/p&gt;

&lt;p&gt;With the automation it became possible to add conditions, like execute the commands only during specific time (night), or using sunrise/sunset events for my location.
Also I was able to add the delay for turning lamps off, to prevent it from flickering back and force too often.&lt;/p&gt;

&lt;p&gt;Automation config:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;1596129224206&apos;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Turn on  the lights on MQTT&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;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;trigger&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;payload&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ON|s1&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;mqtt&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;topic&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;sensors/event&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;condition&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;after&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;21:30&apos;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;before&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;06:00&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;condition&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;time&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;action&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;brightness_pct&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;70&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;device_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;289e562dddbc4af783c23cdcc4a8b9cd&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;light&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;entity_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;light.esszimmer2_lamp&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;turn_on&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;1596130596060&apos;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Turn off the lights on MQTT&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;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;trigger&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;payload&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;OFF|s1&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;mqtt&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;topic&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;sensors/event&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;condition&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;after&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;21:30&apos;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;before&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;06:05&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;condition&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;time&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;action&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;delay&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;00:00:20&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;device_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;289e562dddbc4af783c23cdcc4a8b9cd&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;light&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;entity_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;light.esszimmer2_lamp&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;turn_off&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Tue, 28 Jul 2020 00:00:00 +0000</pubDate>
        <link>http://yaraslav.com//2020/07/28/arduino-hue-motion-detection.html</link>
        <guid isPermaLink="true">http://yaraslav.com//2020/07/28/arduino-hue-motion-detection.html</guid>
        
        <category>arduino</category>
        
        <category>sensors</category>
        
        <category>hue</category>
        
        <category>home automation</category>
        
        
      </item>
    
  </channel>
</rss>
