<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>octoprint on foosel.net</title><link>https://foosel.net/tags/octoprint/</link><description>Recent content in octoprint on foosel.net</description><generator>Hugo</generator><language>en-us</language><copyright>Gina Häußge (foosel)</copyright><lastBuildDate>Tue, 12 Mar 2024 00:00:00 +0000</lastBuildDate><atom:link href="https://foosel.net/tags/octoprint/feed.xml" rel="self" type="application/rss+xml"/><item><title>How to quickly create a header modifying reverse proxy with mitmproxy</title><link>https://foosel.net/til/how-to-quickly-create-a-header-modifying-reverse-proxy-with-mitmproxy/</link><pubDate>Tue, 12 Mar 2024 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-quickly-create-a-header-modifying-reverse-proxy-with-mitmproxy/</guid><description>&lt;p&gt;I&amp;rsquo;m currently in the process of testing some changes on OctoPrint involving its automatic user login via request headers, and
for that needed to quickly set up a reverse proxy that would modify the headers of the requests going to the development server
for some quick testing.&lt;/p&gt;
&lt;p&gt;Specifically, I wanted a quick CLI tool that would allow me to set up a reverse proxy listening on port 5555, forwarding to
&lt;code&gt;http://localhost:5000&lt;/code&gt; while also setting the headers &lt;code&gt;X-Remote-User&lt;/code&gt; to &lt;code&gt;remote&lt;/code&gt; and &lt;code&gt;X-Remote-Host&lt;/code&gt; to &lt;code&gt;localhost:5555&lt;/code&gt;.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I&rsquo;m currently in the process of testing some changes on OctoPrint involving its automatic user login via request headers, and
for that needed to quickly set up a reverse proxy that would modify the headers of the requests going to the development server
for some quick testing.</p>
<p>Specifically, I wanted a quick CLI tool that would allow me to set up a reverse proxy listening on port 5555, forwarding to
<code>http://localhost:5000</code> while also setting the headers <code>X-Remote-User</code> to <code>remote</code> and <code>X-Remote-Host</code> to <code>localhost:5555</code>.</p>
<p>Enter <a href="https://mitmproxy.org/"><code>mitmproxy</code></a>, or more specifically its <code>mitmdump</code> tool, which turned out to be a great tool for this job.</p>
<p>All I needed was to run the following command:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>mitmdump --mode reverse:http://localhost:5000@5555 --modify-headers <span style="color:#e6db74">&#34;/X-Remote-User/remote&#34;</span> --modify-headers <span style="color:#e6db74">&#34;/X-Forwarded-Host/localhost:5555&#34;</span>
</span></span></code></pre></div><p>This does the following:</p>
<ul>
<li><code>--mode reverse:http://localhost:5000@5555</code> sets up a reverse proxy listening on port 5555, forwarding to <code>http://localhost:5000</code></li>
<li><code>--modify-headers &quot;/X-Remote-User/remote&quot;</code> sets the <code>X-Remote-User</code> header to <code>remote</code></li>
<li><code>--modify-headers &quot;/X-Forwarded-Host/localhost:5555&quot;</code> sets the <code>X-Forwarded-Host</code> header to <code>localhost:5555</code></li>
</ul>
<p>With that the <a href="https://community.octoprint.org/t/reverse-proxy-configuration-examples/1107">reverse proxy test page in OctoPrint</a>
turned all green and I could test my changes without having to set up an actual reverse proxy in front of the development server.</p>
]]></content:encoded></item><item><title>How to use Obsidian's Dataview plugin to visualize frontmatter</title><link>https://foosel.net/til/how-to-use-obsidians-dataview-plugin-to-visualize-frontmatter/</link><pubDate>Fri, 14 Apr 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-use-obsidians-dataview-plugin-to-visualize-frontmatter/</guid><description>&lt;p&gt;For every &lt;a href="https://octoprint.org"&gt;OctoPrint&lt;/a&gt; release I run through several update tests: I flash a specific OctoPi version, push it to a specific OctoPrint version, configure the release channel, then see if updating to the newest release (candidate) works fine. I use &lt;a href="https://octoprint.org/blog/2020/07/29/automating-octoprints-release-tests/"&gt;my testrig and its automation scripts&lt;/a&gt; for that and usually go through something between 5 and 10 separate scenarios.&lt;/p&gt;
&lt;p&gt;So far all of these scenarios were noted down as a Markdown table in my release checklist that these days I prepare in my &lt;a href="https://obsidian.md"&gt;Obsidian&lt;/a&gt; vault, including manually adjusting the testrig commands to match the scenario. Having to take care of the latter is something that has been annoying for a long time now, and during the preparation for &lt;a href="https://github.com/OctoPrint/OctoPrint/releases/tag/1.9.0rc5"&gt;yesterday&amp;rsquo;s release candidate&lt;/a&gt; I decided enough is enough and looked into improving my tooling a bit. In the end, I used Obsidian&amp;rsquo;s quite amazing &lt;a href="https://blacksmithgu.github.io/obsidian-dataview/"&gt;Dataview plugin&lt;/a&gt; to query the information about the planned test scenarios from the checklist&amp;rsquo;s frontmatter, build the testrig command from that, then render all of this as a table, complete with some checkboxes for state logging during the tests and a copy button for the command.&lt;/p&gt;</description><content:encoded><![CDATA[<p>For every <a href="https://octoprint.org">OctoPrint</a> release I run through several update tests: I flash a specific OctoPi version, push it to a specific OctoPrint version, configure the release channel, then see if updating to the newest release (candidate) works fine. I use <a href="https://octoprint.org/blog/2020/07/29/automating-octoprints-release-tests/">my testrig and its automation scripts</a> for that and usually go through something between 5 and 10 separate scenarios.</p>
<p>So far all of these scenarios were noted down as a Markdown table in my release checklist that these days I prepare in my <a href="https://obsidian.md">Obsidian</a> vault, including manually adjusting the testrig commands to match the scenario. Having to take care of the latter is something that has been annoying for a long time now, and during the preparation for <a href="https://github.com/OctoPrint/OctoPrint/releases/tag/1.9.0rc5">yesterday&rsquo;s release candidate</a> I decided enough is enough and looked into improving my tooling a bit. In the end, I used Obsidian&rsquo;s quite amazing <a href="https://blacksmithgu.github.io/obsidian-dataview/">Dataview plugin</a> to query the information about the planned test scenarios from the checklist&rsquo;s frontmatter, build the testrig command from that, then render all of this as a table, complete with some checkboxes for state logging during the tests and a copy button for the command.</p>
<p>Here&rsquo;s the format of the frontmatter for the latest release candidate 1.9.0rc5:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">update_test_type</span>: <span style="color:#ae81ff">maintenance</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">update_tests</span>:
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">image</span>: <span style="color:#ae81ff">0.17.0</span>-<span style="color:#ae81ff">py3</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">update_test_type</span>: <span style="color:#ae81ff">simplepip</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">additional</span>: <span style="color:#ae81ff">pip=21.3.1</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">wip</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">done</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">image</span>: <span style="color:#ae81ff">0.18.0</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">channel</span>: <span style="color:#ae81ff">maintenance</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">start</span>: <span style="color:#ae81ff">1.8.7</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">wip</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">done</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">image</span>: <span style="color:#ae81ff">0.18.0</span>-<span style="color:#ae81ff">latest</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">channel</span>: <span style="color:#ae81ff">maintenance</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">wip</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">done</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">image</span>: <span style="color:#ae81ff">1.0.0</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">channel</span>: <span style="color:#ae81ff">maintenance</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">start</span>: <span style="color:#ae81ff">1.8.7</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">wip</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">done</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">image</span>: <span style="color:#ae81ff">1.0.0</span>-<span style="color:#ae81ff">latest</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">channel</span>: <span style="color:#ae81ff">stable</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">wip</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">done</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">image</span>: <span style="color:#ae81ff">1.0.0</span>-<span style="color:#ae81ff">latest</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">channel</span>: <span style="color:#ae81ff">maintenance</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">start</span>: <span style="color:#ae81ff">1.9</span><span style="color:#ae81ff">.0rc3</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">wip</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">done</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">image</span>: <span style="color:#ae81ff">1.0.0</span>-<span style="color:#ae81ff">latest</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">channel</span>: <span style="color:#ae81ff">maintenance</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">start</span>: <span style="color:#ae81ff">1.9</span><span style="color:#ae81ff">.0rc4</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">wip</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">done</span>: <span style="color:#66d9ef">true</span>
</span></span></code></pre></div><p>And this is the <code>dataviewjs</code> query I used:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">createCheckbox</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">checked</span>) =&gt; {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">checkbox</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">this</span>.<span style="color:#a6e22e">container</span>.<span style="color:#a6e22e">createEl</span>(<span style="color:#e6db74">&#34;input&#34;</span>, {<span style="color:#e6db74">&#34;type&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;checkbox&#34;</span>});
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">checkbox</span>.<span style="color:#a6e22e">checked</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">checked</span>;
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">checkbox</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">createDropdown</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">options</span>, <span style="color:#a6e22e">selected</span>) =&gt; {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">dropdown</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">this</span>.<span style="color:#a6e22e">container</span>.<span style="color:#a6e22e">createEl</span>(<span style="color:#e6db74">&#34;select&#34;</span>);
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">options</span>.<span style="color:#a6e22e">map</span>((<span style="color:#a6e22e">value</span>, <span style="color:#a6e22e">idx</span>) =&gt; {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">option</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">dropdown</span>.<span style="color:#a6e22e">createEl</span>(<span style="color:#e6db74">&#34;option&#34;</span>, {<span style="color:#e6db74">&#34;text&#34;</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">value</span>, <span style="color:#e6db74">&#34;value&#34;</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">value</span>});
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">option</span>.<span style="color:#a6e22e">selected</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">value</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">selected</span>);
</span></span><span style="display:flex;"><span>	});
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">dropdown</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">createCopyButton</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">command</span>) =&gt; {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">button</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">this</span>.<span style="color:#a6e22e">container</span>.<span style="color:#a6e22e">createEl</span>(<span style="color:#e6db74">&#34;button&#34;</span>, {<span style="color:#e6db74">&#34;text&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Copy&#34;</span>})
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">button</span>.<span style="color:#a6e22e">addEventListener</span>(<span style="color:#e6db74">&#34;click&#34;</span>, (<span style="color:#a6e22e">evt</span>) =&gt; {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">evt</span>.<span style="color:#a6e22e">preventDefault</span>();
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">navigator</span>.<span style="color:#a6e22e">clipboard</span>.<span style="color:#a6e22e">writeText</span>(<span style="color:#a6e22e">command</span>);
</span></span><span style="display:flex;"><span>	});
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">button</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">DUTS</span> <span style="color:#f92672">=</span> [<span style="color:#e6db74">&#34;pia&#34;</span>, <span style="color:#e6db74">&#34;pib&#34;</span>, <span style="color:#e6db74">&#34;pic&#34;</span>];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">update_type</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">dv</span>.<span style="color:#a6e22e">current</span>().<span style="color:#a6e22e">update_test_type</span> <span style="color:#f92672">||</span> <span style="color:#e6db74">&#34;maintenance&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">tests</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">dv</span>.<span style="color:#a6e22e">current</span>().<span style="color:#a6e22e">update_tests</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">dv</span>.<span style="color:#a6e22e">table</span>([<span style="color:#e6db74">&#34;ID&#34;</span>, <span style="color:#e6db74">&#34;WIP&#34;</span>, <span style="color:#e6db74">&#34;Done&#34;</span>, <span style="color:#e6db74">&#34;DUT&#34;</span>, <span style="color:#e6db74">&#34;OctoPi&#34;</span>, <span style="color:#e6db74">&#34;Channel&#34;</span>, <span style="color:#e6db74">&#34;Start&#34;</span>, <span style="color:#e6db74">&#34;Command&#34;</span>, <span style="color:#e6db74">&#34;&#34;</span>], <span style="color:#a6e22e">tests</span>
</span></span><span style="display:flex;"><span>	.<span style="color:#a6e22e">map</span>((<span style="color:#a6e22e">t</span>, <span style="color:#a6e22e">idx</span>) =&gt; {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">dut</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">dut</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">DUTS</span>[<span style="color:#a6e22e">idx</span> <span style="color:#f92672">%</span> <span style="color:#a6e22e">DUTS</span>.<span style="color:#a6e22e">length</span>];
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">version</span> <span style="color:#f92672">=</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">start</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">start</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;stock&#34;</span>) <span style="color:#f92672">?</span> <span style="color:#e6db74">&#34;&#34;</span> <span style="color:#f92672">:</span> <span style="color:#e6db74">`,version=</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">start</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">let</span> <span style="color:#a6e22e">command</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">switch</span> (<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">update_test_type</span>) {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">case</span> <span style="color:#e6db74">&#34;simplepip&#34;</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">command</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">`fab flashhost_flash_and_provision:</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">image</span><span style="color:#e6db74">}</span><span style="color:#e6db74"> octopi_test_simplepip`</span>;
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">additional</span>) {
</span></span><span style="display:flex;"><span>					<span style="color:#a6e22e">command</span> <span style="color:#f92672">+=</span> <span style="color:#e6db74">&#34;:&#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">additional</span>;
</span></span><span style="display:flex;"><span>				}
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">break</span>;
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">default</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">command</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">`fab flashhost_flash_and_provision:</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">image</span><span style="color:#e6db74">}</span><span style="color:#e6db74"> octopi_test_update_</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">update_type</span><span style="color:#e6db74">}</span><span style="color:#e6db74">:</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">channel</span><span style="color:#e6db74">}${</span><span style="color:#a6e22e">version</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>;
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">additional</span>) {
</span></span><span style="display:flex;"><span>				  <span style="color:#a6e22e">command</span> <span style="color:#f92672">+=</span> <span style="color:#e6db74">&#34;,&#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">additional</span>
</span></span><span style="display:flex;"><span>				}
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">break</span>;
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">wipCheckbox</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">createCheckbox</span>(<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">wip</span>);
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">doneCheckbox</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">createCheckbox</span>(<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">done</span>);
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">dutDropdown</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">createDropdown</span>(<span style="color:#a6e22e">DUTS</span>, <span style="color:#a6e22e">dut</span>);
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">copyButton</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">createCopyButton</span>(<span style="color:#a6e22e">command</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> [<span style="color:#a6e22e">idx</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>, <span style="color:#a6e22e">wipCheckbox</span>, <span style="color:#a6e22e">doneCheckbox</span>, <span style="color:#a6e22e">dutDropdown</span>, <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">image</span>, <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">channel</span>, <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">start</span> <span style="color:#f92672">?</span> <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">start</span> <span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;stock&#34;</span>, <span style="color:#e6db74">&#34;`&#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">command</span> <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;`&#34;</span>, <span style="color:#a6e22e">copyButton</span>]
</span></span><span style="display:flex;"><span>	}));
</span></span></code></pre></div><p>This iterates over the list of scenarios, and for each generates a command, assigns one of the DUTs (Device Under Test) of the testrig, creates checkboxes for WIP and Done seeded from the frontmatter data and also a copy button. This then gets rendered as a table.</p>
<p>Throwing that query in a <code>dataviewjs</code> typed markdown code fence yields a nice rendition of the data that allows me to check if I have forgotten anything, track my progress with the checkboxes and also allows me to easily copy the generated command with the click of a button, so I don&rsquo;t have to manually copy paste stuff anymore:</p>
<p><img alt="A screenshot of a table visualizing the test scenarios. The columns are ID, WIP, Done, DUT, OctoPi, Channel, Start and Command, the rows are the secnarios. Each scenario has a testrig command attached that can be copied with a dedicated button." loading="lazy" src="/til/how-to-use-obsidians-dataview-plugin-to-visualize-frontmatter/scenario-table.png"></p>
<p>What I have not yet figured out is how to manipulate the frontmatter directly via the checkboxes and DUT dropdowns (for logging purposes) - right now I still have to edit the state changes manually to persist them. The <a href="https://github.com/chhoumann/MetaEdit">MetaEdit</a> plugin for Obsidian looked promising, but that doesn&rsquo;t seem to be flexible enough to manipulate a list of items (yet).</p>
<p>In any case, this helped me a ton yesterday, and will save me a lot of time with future releases and release candidates!</p>
]]></content:encoded></item><item><title>How to grep a log for multiline errors</title><link>https://foosel.net/til/how-to-grep-a-log-for-multline-errors/</link><pubDate>Wed, 01 Feb 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-grep-a-log-for-multline-errors/</guid><description>&lt;p&gt;I just found myself in the position to have to &lt;code&gt;grep&lt;/code&gt; an OctoPrint log file for error log entries with attached Python stack traces. I wanted to not only get the starting line where the exception log output starts, but the full stack trace up until the next regular log line.&lt;/p&gt;
&lt;p&gt;The format of the lines in &lt;code&gt;octoprint.log&lt;/code&gt; is a simple &lt;code&gt;%(asctime)s - %(name)s - %(levelname)s - %(message)s&lt;/code&gt;, so a log with an error and attached exception looks like this:&lt;/p&gt;</description><content:encoded><![CDATA[<p>I just found myself in the position to have to <code>grep</code> an OctoPrint log file for error log entries with attached Python stack traces. I wanted to not only get the starting line where the exception log output starts, but the full stack trace up until the next regular log line.</p>
<p>The format of the lines in <code>octoprint.log</code> is a simple <code>%(asctime)s - %(name)s - %(levelname)s - %(message)s</code>, so a log with an error and attached exception looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>2023-01-30 17:50:45,704 - octoprint.events.fire - DEBUG - Firing event: Disconnecting (Payload: None)
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,704 - octoprint.events - DEBUG - Sending action to &lt;bound method PrinterStateConnection._onEvent of &lt;octoprint.server.util.sockjs.PrinterStateConnection object at 0x000001635CB6EE50&gt;&gt;
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,705 - octoprint.plugin - DEBUG - Calling on_event on action_command_notification
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,705 - octoprint.plugin - DEBUG - Calling on_event on action_command_prompt
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,705 - octoprint.plugin - DEBUG - Calling on_event on announcements
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,705 - octoprint.plugin - DEBUG - Calling on_event on file_check
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,706 - octoprint.plugin - DEBUG - Calling on_event on firmware_check
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,706 - octoprint.plugin - DEBUG - Calling on_event on pluginmanager
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,706 - octoprint.plugin - DEBUG - Calling on_event on softwareupdate
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,706 - octoprint.plugin - DEBUG - Calling on_event on tracking
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,711 - octoprint.plugin - DEBUG - Calling on_event on mqtt
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,732 - octoprint.events.fire - DEBUG - Firing event: Disconnected (Payload: None)
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,735 - octoprint.events - DEBUG - Sending action to &lt;function Server.run.&lt;locals&gt;.&lt;lambda&gt; at 0x000001635BDB2CA0&gt;
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,750 - octoprint.events - ERROR - Got an exception while sending event Disconnected (Payload: None) to &lt;function Server.run.&lt;locals&gt;.&lt;lambda&gt; at 0x000001635BDB2CA0&gt;
</span></span><span style="display:flex;"><span>Traceback (most recent call last):
</span></span><span style="display:flex;"><span>  File &#34;C:\Devel\OctoPrint\OctoPrint\src\octoprint\events.py&#34;, line 197, in _work
</span></span><span style="display:flex;"><span>    listener(event, payload)
</span></span><span style="display:flex;"><span>  File &#34;C:\Devel\OctoPrint\OctoPrint\src\octoprint\server\__init__.py&#34;, line 1212, in &lt;lambda&gt;
</span></span><span style="display:flex;"><span>    octoprint.events.Events.DISCONNECTED, lambda e, p: run_autorefresh()
</span></span><span style="display:flex;"><span>                                                       ^^^^^^^^^^^^^^^^^
</span></span><span style="display:flex;"><span>  File &#34;C:\Devel\OctoPrint\OctoPrint\src\octoprint\server\__init__.py&#34;, line 1195, in run_autorefresh
</span></span><span style="display:flex;"><span>    autorefresh.stop()
</span></span><span style="display:flex;"><span>    ^^^^^^^^^^^^^^^^
</span></span><span style="display:flex;"><span>AttributeError: &#39;RepeatedTimer&#39; object has no attribute &#39;stop&#39;
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,753 - octoprint.events - DEBUG - Sending action to &lt;bound method PrinterStateConnection._onEvent of &lt;octoprint.server.util.sockjs.PrinterStateConnection object at 0x000001635CB6EE50&gt;&gt;
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,755 - octoprint.plugin - DEBUG - Calling on_event on action_command_notification
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,756 - octoprint.server.util.sockjs - DEBUG - Socket message held back until permissions cleared, added to backlog: plugin
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,758 - octoprint.plugins.action_command_notification - INFO - Notifications cleared
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,758 - octoprint.plugin - DEBUG - Calling on_event on action_command_prompt
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,758 - octoprint.plugin - DEBUG - Calling on_event on announcements
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,759 - octoprint.plugin - DEBUG - Calling on_event on file_check
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,759 - octoprint.plugin - DEBUG - Calling on_event on firmware_check
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,759 - octoprint.server.util.sockjs - DEBUG - Socket message held back until permissions cleared, added to backlog: plugin
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,763 - octoprint.plugin - DEBUG - Calling on_event on pluginmanager
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,764 - octoprint.plugin - DEBUG - Calling on_event on softwareupdate
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,764 - octoprint.plugin - DEBUG - Calling on_event on tracking
</span></span></code></pre></div><p>What I now wanted is for <code>grep</code> to spit out just the <code>ERROR</code> line and the attached stack trace:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>2023-01-30 17:50:45,750 - octoprint.events - ERROR - Got an exception while sending event Disconnected (Payload: None) to &lt;function Server.run.&lt;locals&gt;.&lt;lambda&gt; at 0x000001635BDB2CA0&gt;
</span></span><span style="display:flex;"><span>Traceback (most recent call last):
</span></span><span style="display:flex;"><span>  File &#34;C:\Devel\OctoPrint\OctoPrint\src\octoprint\events.py&#34;, line 197, in _work
</span></span><span style="display:flex;"><span>    listener(event, payload)
</span></span><span style="display:flex;"><span>  File &#34;C:\Devel\OctoPrint\OctoPrint\src\octoprint\server\__init__.py&#34;, line 1212, in &lt;lambda&gt;
</span></span><span style="display:flex;"><span>    octoprint.events.Events.DISCONNECTED, lambda e, p: run_autorefresh()
</span></span><span style="display:flex;"><span>                                                       ^^^^^^^^^^^^^^^^^
</span></span><span style="display:flex;"><span>  File &#34;C:\Devel\OctoPrint\OctoPrint\src\octoprint\server\__init__.py&#34;, line 1195, in run_autorefresh
</span></span><span style="display:flex;"><span>    autorefresh.stop()
</span></span><span style="display:flex;"><span>    ^^^^^^^^^^^^^^^^
</span></span><span style="display:flex;"><span>AttributeError: &#39;RepeatedTimer&#39; object has no attribute &#39;stop&#39;
</span></span></code></pre></div><p>For this I needed a way to set <code>grep</code> to match multiple lines and do a (non-matching) look ahead for the end. It turns out that the secret to success here is to treat the whole input as one line, use Perl compatible regex mode, and make sure to set the multiline flag. After some fiddling around on <a href="https://regex101.com/r/qYOrnT/1">regex101.com</a> and reading up on <a href="https://perldoc.perl.org/perlre#Extended-Patterns">Perl&rsquo;s regex options</a><sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, I came up with the following:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>grep -Pazo &#39;(?m)^\N+\- ERROR \-\N*\n(^\N*?\n)*?(?=\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3} \- )&#39; octoprint.log
</span></span></code></pre></div><p>Let&rsquo;s walk through this:</p>
<ul>
<li><code>-P</code> enables Perl compatible regex mode</li>
<li><code>-a</code> enables text mode</li>
<li><code>-z</code> turns all newlines into null bytes and thus treats the whole input as a single line for finding matches<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></li>
<li><code>-o</code> only outputs the matched part of the line (otherwise we&rsquo;d get the whole file printed out)</li>
<li><code>(?m)</code> enables multiline mode</li>
<li><code>^\N+\- ERROR \-\N*\n</code> matches the first line of the error, which is the one that starts with the timestamp and package and contains the word <code>ERROR</code></li>
<li><code>(^\N*?\n)*?</code> non-greedily matches all following lines of the error, which are anything but a newline followed by a newline</li>
<li><code>(?=\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3} \- )</code> is a positive look-ahead that matches a line starting with a timestamp again, which signifies the end of the error&rsquo;s lines</li>
</ul>
<p>Hooray, it works 🥳:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>❯ grep -Pazo &#39;(?m)^\N+\- ERROR \-\N*\n(^\N*?\n)*?(?=\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3} \- )&#39; octoprint.log
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,750 - octoprint.events - ERROR - Got an exception while sending event Disconnected (Payload: None) to &lt;function Server.run.&lt;locals&gt;.&lt;lambda&gt; at 0x000001635BDB2CA0&gt;
</span></span><span style="display:flex;"><span>Traceback (most recent call last):
</span></span><span style="display:flex;"><span>  File &#34;C:\Devel\OctoPrint\OctoPrint\src\octoprint\events.py&#34;, line 197, in _work
</span></span><span style="display:flex;"><span>    listener(event, payload)
</span></span><span style="display:flex;"><span>  File &#34;C:\Devel\OctoPrint\OctoPrint\src\octoprint\server\__init__.py&#34;, line 1212, in &lt;lambda&gt;
</span></span><span style="display:flex;"><span>    octoprint.events.Events.DISCONNECTED, lambda e, p: run_autorefresh()
</span></span><span style="display:flex;"><span>                                                       ^^^^^^^^^^^^^^^^^
</span></span><span style="display:flex;"><span>  File &#34;C:\Devel\OctoPrint\OctoPrint\src\octoprint\server\__init__.py&#34;, line 1195, in run_autorefresh
</span></span><span style="display:flex;"><span>    autorefresh.stop()
</span></span><span style="display:flex;"><span>    ^^^^^^^^^^^^^^^^
</span></span><span style="display:flex;"><span>AttributeError: &#39;RepeatedTimer&#39; object has no attribute &#39;stop&#39;
</span></span></code></pre></div><p>(And yes, I&rsquo;ve fixed the error that lead to this stack trace as well 😉)</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>I don&rsquo;t know about you, but I always forget about positive/negative look-ahead/behind and pattern-match modifiers.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>The downside of this is that now <code>-n</code> (print line number of match) will not work anymore and just happily report line 1 for every single match.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item></channel></rss>