<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Bytesonly]]></title><description><![CDATA[Bytesonly]]></description><link>https://bytesonly.com</link><generator>RSS for Node</generator><lastBuildDate>Thu, 09 Apr 2026 08:16:14 GMT</lastBuildDate><atom:link href="https://bytesonly.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[The fabulous world of PAC files]]></title><description><![CDATA[Prelude
This is the story of how I fell into the rabbit hole of proxy automatic configuration (or PAC) files. My company lets me connect to their network by VPN. But we also use cloud services like GitHub. The enterprise GitHub is secured by an IP al...]]></description><link>https://bytesonly.com/the-fabulous-world-of-pac-files</link><guid isPermaLink="true">https://bytesonly.com/the-fabulous-world-of-pac-files</guid><category><![CDATA[#pac]]></category><category><![CDATA[Browsers]]></category><category><![CDATA[intellij]]></category><category><![CDATA[proxy]]></category><dc:creator><![CDATA[Frank]]></dc:creator><pubDate>Sat, 30 Nov 2024 12:23:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/_MauPmUJJ08/upload/454279c7da9cc4bbb7b43ec3e85719eb.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-prelude">Prelude</h2>
<p>This is the story of how I fell into the rabbit hole of proxy automatic configuration (or PAC) files. My company lets me connect to their network by VPN. But we also use cloud services like GitHub. The enterprise GitHub is secured by an IP allow list, so it can only be accessed from legitimate devices. In our case, that traffic comes from the outgoing forward proxy. The VPN somehow doesn't route all traffic through that proxy, so this needs to be configured on the client machine. So far so good, let's get started.</p>
<h2 id="heading-the-simple-solution">The simple solution</h2>
<p>On macOS, you can go to Wi-Fi Settings &gt; Your Wi-Fi name &gt; Proxies. Let's set the HTTP and HTTPS proxy to "<a target="_blank" href="http://proxy.company.ch">proxy.company.ch</a>" and the port to 8080. Now I can access GitHub without any issues. I can work and everything is fine, until I want to google something. Wait, what? <a target="_blank" href="http://Google.com">Google.com</a> gives me an error. And so does pretty much the rest of the internet. I get an ERR_TUNNEL_CONNECTION_FAILED. It looks like the VPN can't resolve any other hostnames than <a target="_blank" href="http://github.com">github.com</a> and a few others. Probably this was configured by hand, or there is no proper DNS resolver configured? I'll probably never know. If there were only a way to tell the system to use the proxy only for certain domains. Enter the world of PAC files.</p>
<h2 id="heading-the-pac-file">The PAC file</h2>
<p>Luckily, the company is hosting a PAC file. The Proxy Auto-Config file defines how web browsers and other user agents can automatically choose the appropriate proxy server for fetching a given URL. The file contains the JavaScript function FindProxyForUrl(url, host), which returns a string with one or more access methods.</p>
<p>Let's look at the file that my company provides:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">FindProxyForURL</span>(<span class="hljs-params">url, host</span>) </span>{
  PROXY = <span class="hljs-string">"PROXY proxy.company.com:8080"</span>;

  <span class="hljs-keyword">if</span> (shExpMatch(host, <span class="hljs-string">"*github.com|*.some-other-domain.com"</span>)) {
    <span class="hljs-keyword">return</span> PROXY;
  }

  <span class="hljs-keyword">return</span> <span class="hljs-string">"DIRECT"</span>;
}
</code></pre>
<p>Cool. Send traffic to github.com and other important domains through the proxy and everything else not.</p>
<p>Let's just use that file. Go to Wi-Fi Settings &gt; Your Wi-Fi name &gt; Proxies. Turn automatic proxy configuration on and set the URL to <a target="_blank" href="https://company.com/proxy.pac">https://company.com/proxy.pac</a> or whatever the link to the PAC file is. As soon as you click OK, the file is loaded from the server. However, in the browser, everything is like before. That's because the browser loads the PAC file on startup. So let's close the browser and open it again. Cool. GitHub.com works and so does the rest of the internet. All fixed, let's get to work. I use IntelliJ, and there is this neat feature to see GitHub pull requests in the IDE. But wait, it's not working. Let's go to the IntelliJ Settings &gt; Proxy and activate "Auto-detect proxy settings." It still doesn't work. Maybe I also need to specify the PAC file location explicitly: click on "Automatic proxy configuration URL" and set the URL there. Maybe the IDE just needs a restart. Still nothing. What domains does the PR plugin connect to? I start a local intercepting proxy and point IntelliJ there, so I can analyze the traffic. It sends some POST requests to <a target="_blank" href="https://api.github.com/graphql">https://api.github.com/graphql</a>.</p>
<p>Isn't that strange? api.github.com/graphql should definitely match *.github.com. Let's verify this online <a target="_blank" href="https://thorsen.pm/proxyforurl">with a PAC file tester</a>. The URL “http://api.github.com/graphql“ returns PROXY, and so does “http://github.com”. Let's try it also <a target="_blank" href="https://pactester.online/">on this page</a> for good measure. Same. How come IntelliJ is not sending this traffic to the proxy, but the browser is?</p>
<p>I fire up a transparent proxy so I can do a man-in-the-middle and analyze the requests and responses in detail: <code>mitmproxy -p 9001 --mode upstream:http://company.proxy.com:8080</code>. There are a couple of requests being made, all of them go to api.github.com .And guess what: The feature itself now works in IntelliJ. So it's not really the proxy behaving weird, and it must have something to do with the PAC handling in IntelliJ. So I create my own PAC file I can play around with.</p>
<h2 id="heading-what-the-pac">What the PAC!</h2>
<p>Let's try to find a PAC configuration that works for my use-case. There are rumors that you can use a local pac file, so I add <code>file:///Users/myuser/proxy.pac</code> in the WIFI settings and make a simple config to redirect all traffic to mitmproxy:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">FindProxyForURL</span>(<span class="hljs-params">url, host</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-string">"PROXY localhost:9001"</span>;
}
</code></pre>
<p>That doesn't so anything. My experience is the same as these guys had <a target="_blank" href="https://serverfault.com/questions/957519/why-does-file-users-username-library-proxy-pac-not-work-in-macos">here</a>. And <a target="_blank" href="https://discussions.apple.com/thread/251395256?sortBy=rank">this discussion</a> never got an answer but 150 up votes. Then I host my own PAC file on a server.</p>
<h2 id="heading-the-hosted-pac">The hosted PAC</h2>
<p>I use the same PAC file and start a webserver in that directory: <code>python3 -m http.server 9001 --bind 127.0.0.1</code></p>
<p>Then I set the PAC config to the hosted file: <a target="_blank" href="http://localhost:9001/proxy.pac"><code>http://localhost:9001/proxy.pac</code></a>. Now that I have a solution, that actually works, I can play around with the config.</p>
<p>I immediately notice that there are a bunch of requests as soon as I click ok:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732969138639/a629c873-6058-49a4-9432-06e413e739cb.png" alt class="image--center mx-auto" /></p>
<p>This makes sense because it needs to fetch the new config. But there is also a request as soon as I open Chrome or IntelliJ. Why is that? I thought that the network interface somehow routes the traffic to the correct location, but that's not how it works. When I specify a PAC file in the Wi-Fi settings AND configure a client application to use the system settings, then the client application gets, parses, and evaluates the PAC file itself.</p>
<p>A PAC file was invented by Netscape in 1996, so it's been around for a long, long time. It's just a JavaScript file with a bunch of helper or utility functions that are available. A PAC file contains the one function FindProxyForURL. Inside the function, helpers like shExpMatch and isInNet are available. For parsing a JS file, you need a JavaScript engine. Luckily, the browser already has a JS engine. It fetches the file, parses it, evaluates the code, and applies the rules for redirecting the traffic. But where do those helper functions come from? Well, they are baked into the browser. They are only available in a small sandbox called pac-sandbox inside the JS engine. This is also the place where the PAC file is executed.</p>
<p>We can see the implementation of the helper function shExpMatch in <a target="_blank" href="https://chromium.googlesource.com/chromium/src/+/refs/heads/main/services/proxy_resolver/pac_js_library.h#116">Chromium</a> and in <a target="_blank" href="https://searchfox.org/mozilla-central/source/netwerk/base/ascii_pac_utils.js#72">Spidermonkey</a></p>
<p>The implementation is the same:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">shExpMatch</span>(<span class="hljs-params">url, pattern</span>) </span>{
  pattern = pattern.replace(<span class="hljs-regexp">/\./g</span>, <span class="hljs-string">"\\."</span>);
  pattern = pattern.replace(<span class="hljs-regexp">/\*/g</span>, <span class="hljs-string">".*"</span>);
  pattern = pattern.replace(<span class="hljs-regexp">/\?/g</span>, <span class="hljs-string">"."</span>);
  <span class="hljs-keyword">var</span> newRe = <span class="hljs-keyword">new</span> <span class="hljs-built_in">RegExp</span>(<span class="hljs-string">"^"</span> + pattern + <span class="hljs-string">"$"</span>);
  <span class="hljs-keyword">return</span> newRe.test(url);
}
</code></pre>
<p>We can see that this implementation can handle the pipe. Let verify this to be sure:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.log(shExpMatch(<span class="hljs-string">"http:test.example1.com"</span>, <span class="hljs-string">"*.example1.com|*.example2.com"</span>)) <span class="hljs-comment">// ==&gt; true</span>
</code></pre>
<p>And what if a client doesn't bring its own JS engine already? Probably they use the <a target="_blank" href="https://github.com/manugarg/pacparser">pacparser library</a>. This one too needs a JS engine and the library already bundles Spidermonkey. So maybe that's what IntelliJ does, maybe they wrote their own implementation.</p>
<p>Whichever is the case, the most likely reason that it doesn't work is that there is a bug in IntelliJ shExpMatch implementation. Let's see if this is the case.</p>
<h2 id="heading-the-bug">The bug</h2>
<p>The expression <code>shExpMatch(host, "*.github.com|*.example.com)</code> evaluates to <code>true</code> in Chrome, Firefox and Pacparser for host github.com and api.github.com. And it evaluates to <code>false</code> in IntelliJ. <a target="_blank" href="https://youtrack.jetbrains.com/issue/IDEA-364083/Proxy-implementation-cant-deal-with-pipe-in-shExpMatch-function-of-a-PAC-file">Finally, we found the bug</a> Without the pipe | is all evaluates to true. It's the pipe that IntelliJ can't handle!</p>
<p>The <code>shExpMatch</code> Function is <a target="_blank" href="https://stackoverflow.com/questions/36362748/exactly-what-kind-of-matching-does-shexpmatch-do">highly suspicious</a>:</p>
<ul>
<li><p>findproxyforurl.com - "Will attempt to match hostname or URL to a specified shell expression"</p>
</li>
<li><p>Microsoft - "The shExpMatch(str, shexp) function returns true if str matches the shexp using shell expression patterns."</p>
</li>
<li><p>Mozilla "Currently, the patterns are shell expressions, not regular expressions."</p>
</li>
</ul>
<p>What the hell is a shell expression anyway? What kind of matching does it do?</p>
<h1 id="heading-the-fix">The fix</h1>
<p>I need to circumvent the pipe. I could just write it like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">if</span> (shExpMatch(host, <span class="hljs-string">"*.github.com) || shExpMatch(host, "</span>*.example.com)
</code></pre>
<p>But I want to stay away from shExpMatch for the rest of my life. So this seems a cleaner more stable solution:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">FindProxyForUrl</span>(<span class="hljs-params">url, host</span>) </span>{
  PROXY = <span class="hljs-string">"PROXY company.proxy.com:8080"</span>;

  <span class="hljs-keyword">if</span>  (dnsDomainIs(host, <span class="hljs-string">"github.com"</span>)) <span class="hljs-keyword">return</span> PROXY;
  <span class="hljs-keyword">if</span>  (dnsDomainIs(host, <span class="hljs-string">"example.com"</span>)) <span class="hljs-keyword">return</span> PROXY;

  <span class="hljs-keyword">return</span> <span class="hljs-string">"DIRECT"</span>;
}
</code></pre>
<h2 id="heading-the-takeaways">The takeaways</h2>
<p>Cool, I have a working solution and learned a ton along the way. My key takeaways are the following:</p>
<ul>
<li><p>If a pac file is specified in the WIFI settings, it doesn't route any traffic per se yet. It's merely a place where client applications can look up the URL to fetch, parse and evaluate the file.</p>
</li>
<li><p>On macOS the pac file only works when it's hosted on a web-server. The local file with <code>file:///path-to-file/proxy.pac</code> has no effect. Now that we know that not the OS parses the file, but the client application, it appropriate to say that Chrome doesn’t support a local pac file. And indeed they removed the local pac file support: <a target="_blank" href="https://issues.chromium.org/issues/40574814">https://issues.chromium.org/issues/40574814</a></p>
</li>
<li><p>In IntelliJ a pac file in UTF-8 only works <a target="_blank" href="https://www.jetbrains.com/help/idea/settings-http-proxy.html">if it doesn't use BOM</a>. One more trap alone the way.</p>
</li>
<li><p>In IntelliJ the shExpMatch Function breaks when there is a pipe (|) in the shell expression</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Building traceroute in javascript]]></title><description><![CDATA[I always found traceroute one of the most interesting network utilities, as it gives you an insight on how data travels through a network. If you ping a server and get a response, it’s easy to forget that the data travelled through a cascade of netwo...]]></description><link>https://bytesonly.com/building-traceroute-in-javascript</link><guid isPermaLink="true">https://bytesonly.com/building-traceroute-in-javascript</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[js]]></category><category><![CDATA[trace]]></category><category><![CDATA[traceroute]]></category><category><![CDATA[Wireshark]]></category><dc:creator><![CDATA[Frank]]></dc:creator><pubDate>Sat, 07 Oct 2023 17:19:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/Q1p7bh3SHj8/upload/4f3ed70f38cf5a5400d3abc5a2a630f4.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I always found traceroute one of the most interesting network utilities, as it gives you an insight on how data travels through a network. If you ping a server and get a response, it’s easy to forget that the data travelled through a cascade of network devices until it reached its destination. If you do a traceroute you see the ip addresses and response times of all devices (bridges, routers and gateways) between you and the server. While this is helpful for a network administrator to debug network issues, I found it interesting to see the flow of data. Even more so when it’s combined with geolocation software: With free tools like <a target="_blank" href="http://visualtraceroute.net/">Open Visual Traceroute</a> or web based versions like <a target="_blank" href="https://www.monitis.com/traceroute/">Online Visual Traceroute</a> you can see the result visualised on a map. But since data packets only have a source and a destination ip, how is it possible to find out the route they take through a network? To understand how traceroute works I decided to create my own in JavaScript using Node.js.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:1400/1*ZYVkuYFBksiHRWZmzbBwZg.png" alt /></p>
<p>A visual traceroute from Russia to Google’s public dns server 8.8.8.8</p>
<h1 id="heading-what-is-traceroute-exactly-and-how-does-it-work"><strong>What is traceroute exactly and how does it work?</strong></h1>
<p>Traceroute is a network utility that sends out a bunch of requests (probes) and collects the responses of all network devices in between. But how come those devices send a response to the sender? Under normal circumstances, a device receives an ip packet, figures out where it needs to go and forwards it. Before doing so it decrements the <a target="_blank" href="https://en.wikipedia.org/wiki/Time_to_live">TTL (time-to-live)</a>value of the ip packet. The TTL value doesn’t really specify a “time” to live, it’s more a “number of hops to live”. A hop is the part of a network path between one network device and another. So if you send a TCP packet with a TTL value of 3, the first network device on the path (let’s say that’s your router at home) will decrease the TTL value to 2 and pass the package on to your ISP. The first device on the ISP’s network will decrease it further to a value of 1 and forward the packet again. The next device in the chain will reduce it again by 1 and the TTL value is now 0. A packet with a TTL value of 0 won’t be passed on anymore. Instead, the device will discard the package. From a sender’s point of view the packet has been dumped somewhere along the way, with no clue of when and where this happened. Luckily most devices are kind enough to let the sender know that they discarded a packet. They send a response using the ICMP protocol with a message that says: “Time-to-live exceeded (Time to live exceeded in transit)”. The <a target="_blank" href="https://subinsb.com/default-device-ttl-values/">default TTL values set by the operating system</a> are high enough to ensure that packets can reach their destination before they die. But if we explicitly set a low TTL value we can trigger a response from devices between us and the destination. This is exactly what traceroute does. It sends out packets with a TTL of 1, 2, 3 and so on until the destination is reached. Each time the packet is able to travel one hop further and every time it expires along the way, we get a response.</p>
<h1 id="heading-lets-see-it-in-action"><strong>Let’s see it in action</strong></h1>
<p>Fire up a packet sniffer like <a target="_blank" href="https://www.wireshark.org/">Wireshark</a>(and set the filter to “udp and ip.addr == 8.8.8.8") or open the console and start tcpdump by typing “sudo tcpdump -v ‘icmp or udp’”. Go to another window in your console and start a traceroute to Google’s public dns server, which has the ip address 8.8.8.8 by executing “traceroute 8.8.8.8”. You will see an output similar to this:</p>
<p><img src="https://miro.medium.com/v2/resize:fit:1246/1*Pn9_R6jE6Hp2ScgM3oICqQ.png" alt /></p>
<p>A traceroute to the server 8.8.8.8</p>
<p>The number on the very left is the hop, followed by the ip address of the device and 3 response times in milliseconds. The idea is that those 3 times give you an idea of the average time. So it takes 9 hops from my PC to reach the server 8.8.8.8. For hops 7 and 8 I got a response from a different ip address, every time I sent a package. Let’s check the captured output of Wireshark to see what happened behind the scenes:</p>
<p><img src="https://miro.medium.com/v2/resize:fit:1400/1*SEHxrUNuyjPXPtTcAPINOg.png" alt /></p>
<p>Wireshark capture of a traceroute to the server 8.8.8.8</p>
<p>We can see that three UDP packets have been sent to the destination 8.8.8.8. All of them have a TTL value of 1. Since they expired on the first hop, my router sent me three (one for each request) ICMP responses with the message “Time-to-live exceeded”. Then traceroute sent three probes with a TTL value of 2, but for these Wireshark never saw a response. Seems like my ISP is blocking ICMP responses on his gateway. A hop like this is called a <a target="_blank" href="https://en.wikipedia.org/wiki/Black_hole_(networking)">black hole</a>. Then traceroute sent UDP packets with a TTL of 3, 4, 5 and so on until it finally got an answer from the destination server (in our case the server with the ip 8.8.8.8). The server usually replies with an ICMP “Destination unreachable (Port unreachable)”. This is because the UDP packets are sent with a destination port number between 33434 and 33534, <a target="_blank" href="https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers">which is the reserved range for traceroute</a> and not a valid port for an application. Pretty simple, but also very clever huh? Let’s see how we could build a tool like that in Node.js. What do we need?</p>
<ol>
<li><p>We need to send UDP packets with a specified TTL.</p>
</li>
<li><p>We need to catch the ICMP responses and measure the elapsed time between request and response. We can use the destination port in the response to distinctively match it to a request and therefore increase the destination port for every request.</p>
</li>
<li><p>Traceroute can resolve a domain name to an ip address and also do a reverse dns lookup for ip addresses to get the symbolic name. So let’s also implement this.</p>
</li>
<li><p>Oh and let’s try to make everything with a maximum of 100 lines of code. Why? Because I’m sure we can!</p>
</li>
</ol>
<h1 id="heading-a-simple-traceroute-implementation-in-nodejs"><strong>A simple traceroute implementation in Node.js</strong></h1>
<p>First, let’s import some modules that we need. Dgram lets us send UDP packets and we use raw-socket to catch the ICMP responses. Dns-then is just a promise wrapper to the dns module that we use for dns lookup and reverse lookup.</p>
<p>Now we build a function that always sends out 3 packets, before increasing the TTL value and the port:</p>
<p>Next, we need to catch the ICMP responses, match the port number to the current request and do a reverse dns lookup if required.</p>
<p>The handleReply function called in the above snippet is in charge of formatting and logging the result. If the destination or the maximum number of hops is reached the program will exit. If not the sendPacket function is called again.</p>
<p>These are all the building blocks we need for the basic functionality of traceroute. Now we stick everything together and we are good to go. <a target="_blank" href="https://github.com/frnkst/traceroute-js">Check and download the complete source code.</a></p>
<p>Let’s run it and see what we get:</p>
<p><img src="https://miro.medium.com/v2/resize:fit:1238/1*OPCvq6YFwA8q8kHMwT3tag.png" alt /></p>
<p>Output of our traceroute implementation</p>
<p>And let’s compare it to the native implementation:</p>
<p><img src="https://miro.medium.com/v2/resize:fit:1246/1*Pn9_R6jE6Hp2ScgM3oICqQ.png" alt /></p>
<p>Output of the built-in traceroute on OSX</p>
<p>As you can see the two traceroutes are very similar, but hops 7 and 8 are different. This is due to the fact that packets are being routed dynamically and don’t always take the same route, i.e. when you run the same traceroute twice you might get two different outputs.</p>
<h1 id="heading-final-thoughts"><strong>Final Thoughts</strong></h1>
<p>This is only a very basic implementation to understand the core concept. The traceroute utility comes with a whole bunch of additional functionality like <a target="_blank" href="https://en.wikipedia.org/wiki/Autonomous_system_(Internet)">ASN lookup</a> or support for different protocols. There are cases where firewalls block the UDP packets and you want to fall back to ECHO_PING for your requests. Play around with the code and implement additional features as you like. I hope you had fun and learned something.</p>
]]></content:encoded></item><item><title><![CDATA[How to sucessfully pass the CKAD exam]]></title><description><![CDATA[The Certified Kubernetes Application Developer (https://training.linuxfoundation.org/certification/certified-kubernetes-application-developer-ckad/) is a great certification to train and prove your Kubernetes skills. For approximately USD 600 you can...]]></description><link>https://bytesonly.com/how-to-sucessfully-pass-the-ckad-exam</link><guid isPermaLink="true">https://bytesonly.com/how-to-sucessfully-pass-the-ckad-exam</guid><category><![CDATA[ckad]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[exam ]]></category><category><![CDATA[Certification]]></category><category><![CDATA[linux foundation]]></category><dc:creator><![CDATA[Frank]]></dc:creator><pubDate>Tue, 03 Oct 2023 19:21:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1696361084074/02979024-8f68-4bf4-b2df-9d33a0f808dc.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The Certified Kubernetes Application Developer (<a target="_blank" href="https://training.linuxfoundation.org/certification/certified-kubernetes-application-developer-ckad/">https://training.linuxfoundation.org/certification/certified-kubernetes-application-developer-ckad/</a>) is a great certification to train and prove your Kubernetes skills. For approximately USD 600 you can get the course and exam. The exam is a 2 hours hands-on exercise where you can prove that you can deploy, scale and debug applications on Kubernetes. I took the exam in May 2022 and passed the first try. Here are a few tips that can help you.</p>
<h3 id="heading-essential-setup"><strong>Essential setup</strong></h3>
<p>Speed is essential. It is very important that if you can't write the commands on top of your head, you have a quick way of performing certain actions.</p>
<p>Take a minute or two to set up your environment properly before starting with the first question. Bookmark the Kubernetes cheat sheet and open the page as soon as the exam starts: <a target="_blank" href="https://kubernetes.io/docs/reference/kubectl/cheatsheet/">https://kubernetes.io/docs/reference/kubectl/cheatsheet/</a>. Copy and paste the following commands. Those three commands allow you to use the k alias for kubectl and have bash autocompletion activated for this alias. You can then type for example <code>k get po a&lt;tab&gt;</code> and it will autocomplete the pods name starting with a.</p>
<pre><code class="lang-bash"><span class="hljs-built_in">source</span> &lt;(kubectl completion bash)
<span class="hljs-built_in">alias</span> k=kubectl
complete -F __start_kubectl k
</code></pre>
<p>I was reading a lot of articles where they suggested setting the following alias to quickly switch between different namespaces:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">alias</span> kn=<span class="hljs-string">'kubectl config set-context --current --namespace'</span>
</code></pre>
<p>I was always using this alias during the labs, but when the exam approached I realised that you sometimes need to save a command to a file. Since I was afraid the command couldn't just run as is, when I set the namespace permanently I decided to ditch the alias for the exam and always specify it explicitly.</p>
<p>Next set up your vim properly.</p>
<pre><code class="lang-bash">vim .vimrc
</code></pre>
<p>and add the following three lines:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">set</span> tabstop=2
<span class="hljs-built_in">set</span> expandtab
<span class="hljs-built_in">set</span> shiftwidth=2
</code></pre>
<p>With these set, you will be able to modify and add to the manifests without getting syntax errors because your YAML files are malformed.</p>
<p>Then set two more environment variables which will make your life much easier.</p>
<pre><code class="lang-bash"><span class="hljs-built_in">export</span> now=<span class="hljs-string">'--grace-period 0 --force'</span>
</code></pre>
<p>You can use it to quickly delete a resource without waiting for Kubernetes for gracefully shut it down. For example: <code>k delete po test $now</code>. This will immediately terminate the pod and save you valuable time.</p>
<p>This next variable is maybe the most important one to set of all the tips above:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">export</span> <span class="hljs-keyword">do</span>=<span class="hljs-string">'--dry-run=client -o yaml'</span>
</code></pre>
<p>You should use it anytime you want to create a manifest from the command line. So instead of doing</p>
<pre><code class="lang-bash">k run test-pod --image=nginx
k get po test-pod -o yaml &gt; test-pod.yaml
k delete po test-pod <span class="hljs-variable">$now</span>
</code></pre>
<p>you can simply use the do variable to have a manifest:</p>
<pre><code class="lang-bash">k run test-pod --image=nginx <span class="hljs-variable">$do</span>
</code></pre>
<p>This will save you a huge amount of time.</p>
<h3 id="heading-always-use-the-command-line-and-only-check-the-docs-if-really-needed"><strong>Always use the command line and only check the docs if really needed</strong></h3>
<p>Try to use the command line to create resources whenever possible. You will be much faster using the CLI instead of searching the documentation. I think I pretty much completed the whole exam without using the documentation. Keep in mind that you can always use the -h flag to get useful examples.</p>
<h3 id="heading-practise-exams"><strong>Practise exams</strong></h3>
<p>I scheduled two practice exams from Killer Shell (<a target="_blank" href="https://killer.sh/">https://killer.sh/</a>) and found it very useful to practise for the real exam. The setup of the practice exams is very close to the real one and you can learn how you perform under time pressure. The practice exams are a bit harder than the real ones, so if you can get a good score in the practice exam you should be ready to rock the real one!</p>
]]></content:encoded></item><item><title><![CDATA[Find a playstation 5 using a python bot]]></title><description><![CDATA[So you want to buy a PS5?
A Playstation 5 is pretty hard to get at the moment. I really wanted one and asked in a couple of stores. They all told me that they have a huge waiting list and won't even add me to it. When searching the internet I found a...]]></description><link>https://bytesonly.com/find-a-playstation-5-using-a-python-bot</link><guid isPermaLink="true">https://bytesonly.com/find-a-playstation-5-using-a-python-bot</guid><category><![CDATA[Python]]></category><category><![CDATA[bot]]></category><category><![CDATA[playstation]]></category><category><![CDATA[PlayStation 5]]></category><category><![CDATA[Scraping]]></category><dc:creator><![CDATA[Frank]]></dc:creator><pubDate>Tue, 03 Oct 2023 19:18:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/FlPc9_VocJ4/upload/ed19fb2f09bd0cc02617c0806a9b4752.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-so-you-want-to-buy-a-ps5"><strong>So you want to buy a PS5?</strong></h3>
<p>A Playstation 5 is pretty hard to get at the moment. I really wanted one and asked in a couple of stores. They all told me that they have a huge waiting list and won't even add me to it. When searching the internet I found a couple of pages where people would notify others when they saw an offer online. One for Switzerland is called <a target="_blank" href="https://www.preispirat.ch/">https://www.preispirat.ch/</a>. But whenever I saw a new post, it was already gone. So I decided to write my own small bot to notify me in Telegram was soon as one is available. I thought I need to wait for a week or so, but within a day I got multiple notification and was able to get a PS5. The setup is super simple and can be used for other shopping endeavours as well.</p>
<p>I checked <a target="_blank" href="https://www.mediamarkt.ch/">https://www.mediamarkt.ch/</a> and saw that the have a handy text in the DOM, which I can use to determine when they are sold out. In case the text won't show up, I'll notify myself in Telegram.</p>
<p><img src="https://www.bytesonly.com/_next/static/media/ps5-not-available.bf9752d3d2513c51029a3582d941e912.png" alt /></p>
<h3 id="heading-the-bot"><strong>The bot</strong></h3>
<p>The bot itself is a simple python script that requests the page and then checks if the sold-out text is not present. In that case it will send the message.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> telegram_send
<span class="hljs-keyword">import</span> requests

url = <span class="hljs-string">"https://www.mediamarkt.ch/de/product/_sony-ps-playstation-5-digital-edition-980-pro-nvme-m-2-ssd-1tb-heatsink-2105983.html"</span>

r = requests.get(url)

<span class="hljs-keyword">if</span> <span class="hljs-string">"Produkt momentan nicht verfügbar"</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> r.text:
    telegram_send.send(messages=[<span class="hljs-string">f"PS5 is available on <span class="hljs-subst">{url}</span>"</span>])
</code></pre>
<h3 id="heading-automation"><strong>Automation</strong></h3>
<p>I was running this on a small cloud server, but you can also run it on your own machine. First install telegram-send <code>pip3 install telegram-send</code> and requests <code>pip3 install requests</code>.</p>
<p>Run <code>telegram-send --configure</code> to configure telegram-send. It will walk you through on how to setup a telegram bot and connect to it.</p>
<p>Check if everything works fine and then setup a cron to run the script every 5 minutes. Edit your crontab:</p>
<pre><code class="lang-bash">$ crontab -e
</code></pre>
<p>and insert this line:</p>
<pre><code class="lang-bash">$ */5 * * * *  python3 /bytesonly/ps5-python.py
</code></pre>
<p>Then sit back and wait:</p>
<p><img src="https://www.bytesonly.com/_next/static/media/ps5-available.f40cc459b57cccbab45c110c8d832d42.png" alt /></p>
<p>Good luck and happy shopping.</p>
]]></content:encoded></item><item><title><![CDATA[Marshall @XmlType annotated java object to a string (without @XmlRoot)]]></title><description><![CDATA[Marshalling and unmarshalling

It's called marshalling when you convert a xml object in memory to a serialized format like a string or file

It's called unmarshalling when you convert a xml file or string to an object in memory (for exmpale to a java...]]></description><link>https://bytesonly.com/marshall-xmltype-annotated-java-object-to-a-string-without-xmlroot</link><guid isPermaLink="true">https://bytesonly.com/marshall-xmltype-annotated-java-object-to-a-string-without-xmlroot</guid><category><![CDATA[Kotlin]]></category><category><![CDATA[xml]]></category><category><![CDATA[Parse]]></category><dc:creator><![CDATA[Frank]]></dc:creator><pubDate>Tue, 03 Oct 2023 19:14:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/O2MdroNurVw/upload/921d9c5808c9db423e0a2a7c20772502.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-marshalling-and-unmarshalling"><strong>Marshalling and unmarshalling</strong></h3>
<ul>
<li><p>It's called <strong>marshalling</strong> when you convert a xml object in memory to a serialized format like a string or file</p>
</li>
<li><p>It's called <strong>unmarshalling</strong> when you convert a xml file or string to an object in memory (for exmpale to a java class)</p>
</li>
</ul>
<h3 id="heading-with-the-xmlroot-annotation"><strong>With the @XmlRoot annotation</strong></h3>
<p>If the class you are trying to marshall has a @XmlRoot annotation it's pretty straight forward to marshall it.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> context: JAXBContext = JAXBContext.newInstance(Person::<span class="hljs-keyword">class</span>.java)
<span class="hljs-keyword">val</span> marshaller = context.createMarshaller()

<span class="hljs-comment">// output pretty printed</span>
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, <span class="hljs-literal">true</span>)

<span class="hljs-keyword">val</span> sw = StringWriter()
marshaller.marshal(person, sw)
println(sw.toString())
</code></pre>
<h3 id="heading-without-a-xmlroot-annotation"><strong>Without a @XmlRoot annotation</strong></h3>
<p>I ran into a case, where I wanted to test a function that has @XmlType annotated java class as an input parameter. The verify function of the Mockk library always returned false, because the nested java object doesn't implement an equals function. Since this java class should be easily serialized I tried to marshall it using JAXB. I got an error saying that the class doesn't have an @XMLRoot annotation, which indeed was true. Since the class is from a third party library I had to find a way to marshall it with only a @XmlType annotation. Turns out this can be done by wrapping the object in a JAXBElement with a QName.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> jaxbElement = JAXBElement&lt;Person&gt;(
    QName(<span class="hljs-string">""</span>, <span class="hljs-string">"person"</span>),
    Person::<span class="hljs-keyword">class</span>.java,
    person
)
</code></pre>
<p>A full example would look like this. Let's say we have the following java class without a @XMLRoot annotation:</p>
<pre><code class="lang-java"><span class="hljs-meta">@XmlAccessorType(XMLAccessType.FIELD)</span>
<span class="hljs-meta">@XmlType(name = "person")</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Person</span> </span>{
  String firstName;
  String lastName;

  Person(String firstName, String lastName) {
    <span class="hljs-keyword">this</span>.firstName = firstName;
    <span class="hljs-keyword">this</span>.lastName = lastName;
  }
}
</code></pre>
<p>Now we can marshall it using the following code:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> person = Person(<span class="hljs-string">"firstName"</span>, <span class="hljs-string">"lastName"</span>)
<span class="hljs-keyword">val</span> context: JAXBContext = JAXBContext.newInstance(Person::<span class="hljs-keyword">class</span>.java)
<span class="hljs-keyword">val</span> marshaller = context.createMarshaller()
<span class="hljs-keyword">val</span> sw = StringWriter()
<span class="hljs-keyword">val</span> jaxbElement = JAXBElement&lt;Person&gt;(QName(<span class="hljs-string">""</span>, <span class="hljs-string">"person"</span>), Person::<span class="hljs-keyword">class</span>.java, person)

marshaller.marshal(jaxbElement, sw)
println(sw.toString())
</code></pre>
]]></content:encoded></item></channel></rss>