<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Matt Pallissard</title>
	<link>https://pallissard.net/posts/</link><atom:link href="https://pallissard.net/posts/index.xml" rel="self" type="application/rss+xml" />
	<item>
      <title>Enforce terraform managed AWS tags</title>
      <link>https://pallissard.net/2022/10/19/opa_terraform/</link>
      <pubDate>2022-10-19</pubDate>
      <guid>https://pallissard.net/2022/10/19/opa_terraform/</guid>
      <description>&lt;h1 id=&#34;opa&#34;&gt;OPA&lt;/h1&gt;
&lt;p&gt;You may or may not have heard of &lt;a href=&#34;https://www.openpolicyagent.org/&#34;&gt;Open Policy Agent&lt;/a&gt;.  Basically it&amp;rsquo;s a tool for crafting policies.  It&amp;rsquo;s generalized enough that you can use it for most anything, such as ensuring that any resources created via terraform have required tags.&lt;/p&gt;
&lt;h2 id=&#34;rego&#34;&gt;Rego&lt;/h2&gt;
&lt;p&gt;OPA uses Rego&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, which is in the familiy of logic programming languages.  Of those, &lt;a href=&#34;https://en.wikipedia.org/wiki/Prolog&#34;&gt;Prolog&lt;/a&gt; is probably the one with most recognition.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve never dabbled in logic languages I highly recommend going through &lt;a href=&#34;https://www.swi-prolog.org/GetStarted.html&#34;&gt;one of swi prolog intros&lt;/a&gt; or pragprog has a decent intro to gnu prolog in &lt;a href=&#34;https://pragprog.com/titles/btlang/seven-languages-in-seven-weeks/&#34;&gt;seven languages in seven weeks&lt;/a&gt;.  Unfortunately prolog has several dialects that are kinda-sorta the same, but differ enough to be &lt;em&gt;a thing&lt;/em&gt;.  Much like lisp.&lt;/p&gt;
&lt;p&gt;Rego&amp;rsquo;s syntax is not the same as prolog.  But in line with logic programming, your statements aren&amp;rsquo;t evaluated imperatively.  You define the problem and let the language work out the details.&lt;/p&gt;
&lt;h3 id=&#34;example&#34;&gt;example&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: The boiler plate from this example was copied straight out of the &lt;a href=&#34;https://www.openpolicyagent.org/docs/latest/&#34;&gt;rego documentation&lt;/a&gt;&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; which is excellent overall.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll notice below that there are three functions named &lt;code&gt;match&lt;/code&gt;; a conditional selection of which to execute.  So our match functions will be evaluated based on whether there is a &lt;code&gt;tags&lt;/code&gt; object or a key named &lt;code&gt;foo&lt;/code&gt; or &lt;code&gt;bar&lt;/code&gt; in &lt;code&gt;tags&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So the hand-wavy tl&amp;rsquo;dr;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;changes walks the tree and as a side effect assigns all the resource keys in the object &lt;code&gt;resource_changes&lt;/code&gt; to array &lt;code&gt;c&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;match checks for the conditions as outlined above&lt;/li&gt;
&lt;li&gt;the order this is all invoked in is dictated by the language itself&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pretty straightforward when you look at the policy itself.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#./policy/tags.rego
package terraform.tags

deny[msg] {
	match(changes[c].change.after)
	msg := sprintf(&amp;#34;fail: %v is missing required tags.&amp;#34;, [changes[c].address])
}

match (i) {
	not i.tags
}

match(i) {
	not i.tags.foo
}

match(i) {
	not i.tags.bar
}

changes := { c |
	some path, value
	walk(input, [path, value])
	reverse_index(path, 1) == &amp;#34;resource_changes&amp;#34;
	c = value[_]
}

reverse_index(path, idx) = value {
	value := path[count(path) - idx]
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And the &lt;code&gt;deny&lt;/code&gt; policy above is invoked by the cli itself.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;generate a terraform plan&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;run opa telling it to
&lt;ul&gt;
&lt;li&gt;look in the policy directory for rego files&lt;/li&gt;
&lt;li&gt;evoke the deny policy in the &lt;code&gt;terraform.tags&lt;/code&gt; package&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;~  terragrunt plan -out=./terragrunt.plan &amp;amp;&amp;amp; terragrunt show -json ./terragrunt.plan &amp;gt; terragrunt.json
~  opa exec --decision terraform/tags/deny --bundle policy terragrunt.json 2&amp;gt; &amp;gt;(jq)
{
  &amp;#34;result&amp;#34;: [
    {
      &amp;#34;path&amp;#34;: &amp;#34;terragrunt.json&amp;#34;,
      &amp;#34;result&amp;#34;: [
        &amp;#34;fail: module.rds.module.db_instance.aws_db_instance.this[0] is missing required tags.&amp;#34;,
        &amp;#34;fail: module.rds.module.db_parameter_group.aws_db_parameter_group.this[0] is missing required tags.&amp;#34;,
        &amp;#34;fail: module.security-group.aws_security_group.default[0] is missing required tags.&amp;#34;
      ]
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;ci&#34;&gt;CI&lt;/h2&gt;
&lt;p&gt;Now you could wire that up in your right into your CI process using the CLI if that tickles your fancy.  However, there are tools out there&lt;sup id=&#34;fnref1:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; that support opa policies by default.  It&amp;rsquo;s worth looking into.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Or web assembly interestingly enough.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://www.openpolicyagent.org/docs/latest/terraform/&#34;&gt;https://www.openpolicyagent.org/docs/latest/terraform/&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;I&amp;rsquo;m using terragrunt in this circumstance, but the process would be identical for terraform&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref1:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
	<item>
      <title>The cost of partial automation</title>
      <link>https://pallissard.net/2022/09/19/automation_and_value/</link>
      <pubDate>2022-09-19</pubDate>
      <guid>https://pallissard.net/2022/09/19/automation_and_value/</guid>
      <description>&lt;h1 id=&#34;excuses&#34;&gt;Excuses&lt;/h1&gt;
&lt;p&gt;These days businesses and teams are geared toward cranking out work as quickly as possible.  From what I&amp;rsquo;ve seen, the prevailing sentiment is that time not spent adding features, or ticking check boxes is wasted time.  Often this means that;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;POC/spike, design, and implementation phases of a project are conflated .&lt;/li&gt;
&lt;li&gt;Something that &lt;em&gt;mostly&lt;/em&gt; works is labeled as good enough.&lt;/li&gt;
&lt;li&gt;Improving internal tooling is deferred.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The most common reasons I&amp;rsquo;ve heard in order of increasing comminality;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;They have never seen (and therefore don&amp;rsquo;t recognize the value) of true end-to-end automation.&lt;/li&gt;
&lt;li&gt;Are unable to articulate the values of proper design, planning, and execution to the organization as a whole.&lt;/li&gt;
&lt;li&gt;Thinking they do not have enough or it&amp;rsquo;s not worth their time to properly design and automate a solution.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There is an old analogy that comes in several flavors; a butcher, woodcutter, etc is busy.  Their blade is dull, which slows them down.  But since they are moving so slow they &lt;em&gt;think&lt;/em&gt; that there isn&amp;rsquo;t time to stop and sharpen the blade.&lt;/p&gt;
&lt;p&gt;As such we see people spinning their wheels.  And since nobody likes spinning their wheels, we see &lt;del&gt;excuses&lt;/del&gt; business justifications for wheel spinning disguised as truisms on an almost daily bases.&lt;/p&gt;
&lt;p&gt;20% of the work gets you 80% of the way there. Premature optimization is the root of all evil.  And my personal favorite; Minimal. Viable. Product. &lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h1 id=&#34;but-that-logic-is-flawed&#34;&gt;But that logic is flawed.&lt;/h1&gt;
&lt;p&gt;I&amp;rsquo;m going to call B.S. on they way most orgs use the term MVP.  In essence it&amp;rsquo;s great.  You want to lay out some constraints to avoid over-engineering and wasting time on uneeded features? Cool, that keeps us on task.  But, more often than not, it&amp;rsquo;s used to mark tricky features that aren&amp;rsquo;t complete as out of scope.&lt;/p&gt;
&lt;h2 id=&#34;super-common-workflows-labeled-good-enough&#34;&gt;Super common workflows labeled &amp;ldquo;good enough&amp;rdquo;&lt;/h2&gt;
&lt;p&gt;There are a lot of IaC and CI/CD automation design patterns that get labeled and marketed as good enough.  What winds up happening is each portion of a design is treated as it&amp;rsquo;s own little automated unit, rather than the entire process as a whole.&lt;/p&gt;
&lt;p&gt;For deployment changes one might look like this&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PR for terraform module&lt;/li&gt;
&lt;li&gt;PR for terraform implementation/deployment, cdktf, terragrunt, vanilla terraform&lt;/li&gt;
&lt;li&gt;PR for helm chart&lt;/li&gt;
&lt;li&gt;PR for chart deployment&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And for changes to a core library, it might look like this.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PR for npm/pip package&lt;/li&gt;
&lt;li&gt;PR for downstream code, of which there is often several&lt;/li&gt;
&lt;li&gt;PR for helm chart&lt;/li&gt;
&lt;li&gt;PR for chart deployment&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And all of those little steps of opening and closing PR&amp;rsquo;s are hand-waved over since they&amp;rsquo;re &amp;ldquo;automated&amp;rdquo;.&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h2 id=&#34;napkin-math-cost-breakdown-with-conservative-numbers&#34;&gt;Napkin math cost breakdown with conservative numbers&lt;/h2&gt;
&lt;p&gt;When you start operating automation at scale&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; those little bits that were hand-waved over really start to add up.&lt;/p&gt;
&lt;p&gt;Lets pretend we have engineers with;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The average base salary in Seattle, $150,000&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;401k match and insurance cost the employer $15,000 a year&lt;/li&gt;
&lt;li&gt;And we&amp;rsquo;ll omit equipment, travel, reimbursments, and the costs of HR and tech support.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&amp;rsquo;s a cost of ~$80 dollars an hour to the org.&lt;/p&gt;
&lt;p&gt;Assuming each PR approval/merge and subsequent CI run takes 10 minutes (and there are no mistakes) you&amp;rsquo;re looking at 40 minutes for a change.&lt;/p&gt;
&lt;p&gt;If an engineer makes two non-trivial changes a day&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;, that&amp;rsquo;s $104.   $104 just fiddling with VCS/ci Web-UI&amp;rsquo;s, copy-pasting data, and running cli commands.&lt;/p&gt;
&lt;p&gt;Lets also pretend that they take several weeks off this year.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$80 * 1.3hours * 5days * 45 weeks
23400.0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since this is napkin math, we&amp;rsquo;ll round down.  $23k in overhead a year, for &lt;em&gt;each engineer&lt;/em&gt;.  $23k a year that is adding &lt;em&gt;exactly $0 in value&lt;/em&gt; to the organization.  In fact that is money going right down the drain.&lt;/p&gt;
&lt;h2 id=&#34;and-that-was-conservative&#34;&gt;And that was conservative&lt;/h2&gt;
&lt;p&gt;Not to mention steps like this are error prone as they aren&amp;rsquo;t tightly coupled.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The PR&amp;rsquo;s have to be opened and merged in a specific order.&lt;/li&gt;
&lt;li&gt;There is often data that has to be moved from one step to another. Manual processes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are going to be bugs caught by unit tests, and performance regressions, and rollbacks&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;.  Things such as dns/endpoint names, services, account information, roles, secrets etc need to be shuffled around.   It&amp;rsquo;s all to common to see organizations use terraform to deploy infrastructure and then manually copy-paste information into vault or a values file in helm.  Or to set permissions on objects in S3 manually.  Or to manually create database or IaM credentials.&lt;/p&gt;
&lt;p&gt;If you are a DevOps engineer, work with 6 other engineers in your organization, and automate all of the ticky-tack little steps that take them 10 to 15 minutes, several times a day;&lt;/p&gt;
&lt;p&gt;Congratulations, you improved 6 peoples day-to-day lives and it is as if the org now has an additional head count without the added cost or time investement in onboarding/ramp up time.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Not that these expressions don&amp;rsquo;t have a use.  They&amp;rsquo;re just applied incorrectly.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;Which is so strange, because there are often really straightforward ways to tie that all together.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;And I&amp;rsquo;m not just talking BigCo web scale.  You can have a small product and support and build tooling for a lot of teams.  Scaling is not necissarily tied to your customer base.  And conversely, you can have a product that is hammered hard but requires next to no infrastructure automation. &lt;code&gt;Size != complexity of a given problem domain&lt;/code&gt;&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34;&gt;
&lt;p&gt;In 2022.&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34;&gt;
&lt;p&gt;Because if the&amp;rsquo;re only doing a bunch of little trivial things, why don&amp;rsquo;t they have the time to automate it all in the first place?&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34;&gt;
&lt;p&gt;And if it&amp;rsquo;s not 100% automated, rollbacks and hot-fixes can be awfully unforgiving.&amp;#160;&lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
	<item>
      <title>Code Golf: Dedup an array of unsigned integers</title>
      <link>https://pallissard.net/2022/09/15/code_golf_dedup_one/</link>
      <pubDate>2022-09-15</pubDate>
      <guid>https://pallissard.net/2022/09/15/code_golf_dedup_one/</guid>
      <description>&lt;p&gt;I stumbled across some code golf the other day.  A fairly typical problem.  Remove duplicates from an array/stream/whatever.  Rather than use a set or other typical data structure that only allows one entry, I thought it&amp;rsquo;d be fun to convert the number to base 2&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;uint64_t get_mask(uint64_t i) {
	return(pow(2, i));
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then use a bitwise or to store the result in an array.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;void insert(uint64_t i, uint64_t *b) {
	*b |= get_mask(i);
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then checking for the existence is a bit shift and bitwise and.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;uint64_t member(uint64_t i, uint64_t *b) {
	return (*b &amp;gt;&amp;gt; i) &amp;amp; 1;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Anyway, here&amp;rsquo;s the whole thing&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#include &amp;lt;stdbool.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;stdint.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;math.h&amp;gt;

enum { LEN = 8 };

uint64_t get_mask(uint64_t i) {
	return (pow(2, i));
}

bool member(uint64_t i, uint64_t *b) {
	return (*b &amp;gt;&amp;gt; i) &amp;amp; 1;
}

void insert(uint64_t i, uint64_t *b) {
	*b |= get_mask(i);
}
void display(uint64_t *t, int i, int j) {
	while (i &amp;lt;= j)
		printf(&amp;#34;%ld &amp;#34;, t[i++]);
	printf(&amp;#34;\n&amp;#34;);
}

int main(void) {
	uint64_t i[] = { 9, 4, 6, 32, 5, 9, 8, 2, 1, 7, 8, 12, 11, 8, 9, 25 };
	uint64_t b[LEN] = {};
	size_t h = sizeof(i) / sizeof(uint64_t);
	int j = 0, k = 0, rl = h - 1;
	display(i, 0, rl);
	do {
		if (!member(i[j], b)) {
			i[k++] = i[j];
			insert(i[j], b);
		} else
			rl--;
	} while (j++ &amp;lt;= h);
	display(i, 0, rl + 1);
}
&lt;/code&gt;&lt;/pre&gt;</description>
    </item>
    
	<item>
      <title>Is it really automated?</title>
      <link>https://pallissard.net/2022/08/29/is_it_really_automated/</link>
      <pubDate>2022-08-29</pubDate>
      <guid>https://pallissard.net/2022/08/29/is_it_really_automated/</guid>
      <description>&lt;p&gt;This is written in the context of IaC, code deployments, and cloud vendors.&lt;/p&gt;
&lt;h1 id=&#34;what-is-automation&#34;&gt;What is automation?&lt;/h1&gt;
&lt;p&gt;From &lt;a href=&#34;https://en.wikipedia.org/wiki/Automation&#34;&gt;Wikipedia&lt;/a&gt;,&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Automation describes a wide range of technologies that reduce human intervention in processes.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Cool.  I&amp;rsquo;ve always been more of an enabler than an interventionist.  Sounds like a good time, sign me up.   And from the same Wikipedia page;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;The main advantages of automation are:

* Increased throughput or productivity
* Improved quality
* Increased predictability
* Improved robustness (consistency), of processes or product
* Increased consistency of output
* Reduced direct human labor costs and expenses
* Reduced cycle time
* Increased accuracy
* Relieving humans of monotonously repetitive work
* Required work in development, deployment, maintenance, and operation of automated processes — often structured as “jobs”
* Increased human freedom to do other things
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;All good things, all good things&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;But lets narrow this down a bit to the things I think about when discussing automation.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Increased throughput or productivity&lt;/li&gt;
&lt;li&gt;Relieving humans of monotonously repetitive work&lt;/li&gt;
&lt;li&gt;Increased human freedom to do other things&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So given the things that &lt;em&gt;I&lt;/em&gt; care about, our productivity and throughput should be increased.  I shouldn&amp;rsquo;t have to do the same thing &lt;em&gt;over and over&lt;/em&gt; again, and I should be able to go off and solve new problems.&lt;/p&gt;
&lt;p&gt;Am I alone in feeling that this is &lt;strong&gt;not&lt;/strong&gt; the current state of DevOps, infrastructure, or software engineering as a whole?&lt;/p&gt;
&lt;h1 id=&#34;framing-of-the-problem&#34;&gt;Framing of the problem&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Conway%27s_law&#34;&gt;Conway&amp;rsquo;s Law&lt;/a&gt; is an interesting lens to look at automation tooling.  It states;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization&amp;rsquo;s communication structure.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Looking at a few popular IaC tools.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;tool&lt;/th&gt;
&lt;th&gt;original author&lt;/th&gt;
&lt;th&gt;author background&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;puppet&lt;/td&gt;
&lt;td&gt;Luke Kanies&lt;/td&gt;
&lt;td&gt;Unix administrator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;saltstack&lt;/td&gt;
&lt;td&gt;Thomas Hatch&lt;/td&gt;
&lt;td&gt;data center architect and systems administrator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;terraform&lt;/td&gt;
&lt;td&gt;Mitchel Hashimoto&lt;/td&gt;
&lt;td&gt;software developer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;helm&lt;/td&gt;
&lt;td&gt;Dies labs&lt;/td&gt;
&lt;td&gt;tools for enabling software developers to quickly deploy to the cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Puppet and Saltstack tend to be geared towards wiring up many disparate parts into a cohesive application or deployment.  While Terraform is built to wire up disparate parts as well, it and helm are geared towards enabling people to leverage cloud technologies, primarily developers.  Purpose and background are not exactly communication structures.   But much like communication structures, they seem to be functions of how each role traditionally operates.&lt;/p&gt;
&lt;p&gt;Despite the differences, they share common traits.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;These tools accept a description of end state&lt;/li&gt;
&lt;li&gt;From that description a delta between what exists and what must exist is created&lt;/li&gt;
&lt;li&gt;Actions are taken to resolve said delta&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But the specific focus of the tooling naturally drives design decisions.  As Terraform and Helm are designed to facilitate rapid cloud deployments they are naturally built to describe individual applications or stacks.  So defining my hand-wavy application &lt;em&gt;foo&lt;/em&gt; in terraform could look something like so.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# foo.tf
terraform {
  required_providers {
    bar = {},
    baz = {}
}}

provider &amp;#34;bar&amp;#34; { }
provider &amp;#34;baz &amp;#34; { }

resource &amp;#34;bar_thing&amp;#34; &amp;#34;bar&amp;#34; {
}

resource &amp;#34;baz_thing&amp;#34; &amp;#34;baz&amp;#34; {
)]}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While most use puppet and Saltstack in a similar manner&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, you are able to break things down into their constituent parts and stitch them back together cohesively.&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;  Which when you&amp;rsquo;re in the business of making a lot of changes en masse, is a godsend.  The same application &lt;em&gt;foo&lt;/em&gt; could look like this in salt stack.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Yep, an empty file
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Because we&amp;rsquo;d apply &lt;em&gt;bar&lt;/em&gt; and &lt;em&gt;baz&lt;/em&gt; to everything and use separate data to drive it, if and only if the data to drive it was defined.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;{% if bar is defined %}
bar_thing:
  bar: bar.options.whatever
{% endif %}
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;{% if baz is defined %}
baz_thing:
  baz: baz.options.whatever
{% endif %}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We&amp;rsquo;d tell salt to walk a tree like this, merging the data basically in what basically amounts to a nested for-loop.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;.
├── bar
│   ├── default.yml
│   └── foo
│       └── file.yml
└── baz
    ├── default.yml
    └── foo
        └── file.yml
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And at that point you have a really nice and tidy taxonomy, if you need to change something about baz universally you have a one-stop-shop.  So rather than having to copy-paste even a small amount of boiler plate and tweak the corresponding bits or feed them in as environment variables at build time, you&amp;rsquo;re relying on a series of two way merges to take a default configuration and augment or override specific parts of it.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# augments
./bar/foo/file.yml
bar:
  options:
    b: 2
./bar/default.yml
bar:
  options:
    a: 1
./baz/foo/file.yml
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# overrides
baz:
  options:
    a: b
./baz/default.yml
baz:
  options:
    a: a
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Which by the way, is all the way &lt;a href=&#34;https://en.wikipedia.org/wiki/Don%27t_repeat_yourself&#34;&gt;DRY&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id=&#34;dry-more-like-damp&#34;&gt;DRY? More like damp.&lt;/h1&gt;
&lt;p&gt;Whether or not folks like to organize their taxonomy by application or it&amp;rsquo;s constituent parts, I&amp;rsquo;ve noticed that I&amp;rsquo;m not alone in the fact that lots of people like the idea of DRY&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;.  There are even tools that set out to address this.  &lt;a href=&#34;https://github.com/helmfile/helmfile&#34;&gt;Helmfile&lt;/a&gt;&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt; and &lt;a href=&#34;https://terragrunt.gruntwork.io/&#34;&gt;Terragrunt&lt;/a&gt; are prime examples. Terragrunt is particularly interesting because it heavily markets itself as a DRY tool.&lt;/p&gt;
&lt;p&gt;Terragrunt lends itself to a hierarchy, in fact, it was completely designed to cut down on duplicated terraform code.  Another interesting bit is rather than traverse down or across a directory tree, it walks it upwards, so you can do something like this.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;include {
  path = find_in_parent_folders()
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And it&amp;rsquo;ll walk up the tree looking for any &lt;code&gt;terragrunt.hcl&lt;/code&gt; folders.  It&amp;rsquo;s a little annoying to have to have that include statement in every &lt;code&gt;terragrunt.hcl&lt;/code&gt; file, and it&amp;rsquo;s not really in the spirit of DRY, but I&amp;rsquo;ll play along.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[modified for clarity]
time=2022-08-25T16:16:07-08:00 level=error msg=/home/matt/example/foo/bar/baz/example.hcl
includes /home/matt/example/terraform/foo/bar/example.hcl, 
which itself includes /home/matt/example/foo/example.hcl. 
Only one level of includes is allowed.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Just kidding, &lt;a href=&#34;https://github.com/gruntwork-io/terragrunt/issues/814&#34;&gt;it can&amp;rsquo;t actually do that&lt;/a&gt;.  But to be fair there is a commonly used yaml merging workaround, much like my salt example above.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;inputs = merge(
  yamldecode(
    file(&amp;#34;${get_terragrunt_dir()}/${find_in_parent_folders(&amp;#34;foo.yml&amp;#34;, local.defaults)}&amp;#34;),
  ),
  yamldecode(
    file(&amp;#34;${get_terragrunt_dir()}/${find_in_parent_folders(&amp;#34;bar.yml&amp;#34;, local.defaults)}&amp;#34;),
  ),
)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But this is not &lt;em&gt;exactly&lt;/em&gt; like my example above.  There is a subtle difference.  Remember how Terragrunt walks &lt;em&gt;up&lt;/em&gt; the directory?  This means that you have to define the yaml you&amp;rsquo;re going to merge at the bottom of the tree.  So rather than being able to put in the same elements to override configuration or complements to augment it, you have to put in a big &amp;lsquo;ol block of boilerplate in every application definition.&lt;/p&gt;
&lt;p&gt;Walking up has another subtle difference.  Any given node can only have one parent.  When walking down, you have  multiple children and your are afforded more taxonomical options.&lt;/p&gt;
&lt;p&gt;This goes back to how the tools were designed to be used.  A way to lower the barrier for entry to cloud automation, not a means of centrally managing large swaths of deployments.&lt;/p&gt;
&lt;p&gt;Admittedly, Helmfile does an alright job of this by being able to import and merge pull in some base configuration and merge input values.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;bases:
  - ../../thing1.yml
  - ../thing2.yml
values:
  - ./default/prod.yml
  - ./{{ .Environment.Name }}.yml
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But you&amp;rsquo;re still left with the problem of boilerplate and the additional issue of relative paths.&lt;/p&gt;
&lt;h1 id=&#34;event-driven-decisions&#34;&gt;Event driven decisions&lt;/h1&gt;
&lt;p&gt;Due to the agentless nature of these cloud provisioning IaC tools, event driven changes are impossible.  You want a something to happen and a modification happens on the fly? You can&amp;rsquo;t express that in these tools. You&amp;rsquo;re going to have to use additional tooling outside of your IaC.  You might be able to describe the additional automation with your IaC, but it will still live outside the tool itself.&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;For a contrived example; say you have a service that has metrics you can query, and based on those metrics you want to horizontally scale a database preventively.  There&amp;rsquo;s a lot of requests in the queue that will eventually work their way through the system as database read operations.  You simply cannot do that without a service that knows how to;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;check metrics&lt;/li&gt;
&lt;li&gt;determine that an event has happened&lt;/li&gt;
&lt;li&gt;match that event with an action&lt;/li&gt;
&lt;li&gt;generate a delta between the current state and the end state defined by the action&lt;/li&gt;
&lt;li&gt;take actions to resolve the delta&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This looks the similar to the list above, with one notable exception.  This delta isn&amp;rsquo;t generated from a declarative configuration, it&amp;rsquo;s generated from an event.  The event handler is generated from a declarative configuration, but the actual action is triggered by a listener.  Which naturally has to be running if it&amp;rsquo;s going to be listening.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not the only person who&amp;rsquo;s taken note of trade off&amp;rsquo;s one makes with agentless configuration.  For example, CI and CD tools such as ArgoCD are always on.  It&amp;rsquo;s listening for changes in a git repo to deploy applications to Kubernetes.  It&amp;rsquo;s a great alternative to wiring up a bunch of weird CI jobs.&lt;/p&gt;
&lt;h1 id=&#34;but-then-we-separate-things&#34;&gt;But then we separate things&lt;/h1&gt;
&lt;p&gt;But it&amp;rsquo;s yet another tool that needs to be configured.   And what I&amp;rsquo;ve seen in practice is users like to separate their code by framework or tool.  So it&amp;rsquo;s terraform modules into one repo, their terraform configuration into a separate one, their application in another, and their helm configuration in yet another.  Then if you want to add your helm configuration to ArgoCD, it&amp;rsquo;s yet another PR.  I&amp;rsquo;m not a proponent of large, all encompassing mono-repos&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;, but some amount of grouping can be sensible.&lt;/p&gt;
&lt;p&gt;If everything is grouped by framework or tool, rather than how it&amp;rsquo;s commonly interacted with, human intervention is almost always required.   Let&amp;rsquo;s say I have to change an application.  Modifying it  it to consume a new external service.  I&amp;rsquo;ll potentially have to open four pull requests and merge them in a very specific order.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;build or modify a terraform module&lt;/li&gt;
&lt;li&gt;implement a change in terraform&lt;/li&gt;
&lt;li&gt;modify the application&lt;/li&gt;
&lt;li&gt;change the helm chart&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sometimes more are required if it is a multi-step change or we have to wait for cloud provisioning to finish.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s easy to get the dependency ordering wrong and all too common to see &amp;rsquo;no-op&amp;rsquo; PRs created with the sole purpose of triggering another CI after something fails.  Not to mention it&amp;rsquo;s completely mundane and repetitive.  I&amp;rsquo;m not saying this workflow is completely broken, the deployments themselves are reproducible which is great.  But calling this automated is disingenuous as there are a lot of repetitive tasks that could easily go away with a little bit of up-front planning.&lt;/p&gt;
&lt;p&gt;But planning is hard to do if you working fast and conflate your design and implementation phases.  Figuring it out as you go.  It&amp;rsquo;s a lot &amp;ldquo;easier&amp;rdquo;, well at first anyways, to write a bunch of little things that you can reason about without a clear big picture.  But the trade off is now you&amp;rsquo;ve de-coupled everything and will need to shuffle data in and out of your little units&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt;.  Which typically winds up being a manual process or some really brittle automation, which is just a manual process in disguise.&lt;/p&gt;
&lt;h1 id=&#34;and-again-with-automation&#34;&gt;And again with automation&lt;/h1&gt;
&lt;p&gt;If we look back to my personal three items&lt;/p&gt;
&lt;h2 id=&#34;increased-throughput-or-productivity&#34;&gt;Increased throughput or productivity&lt;/h2&gt;
&lt;p&gt;With many cloud provisioning tools the initial lift, learning curve, cognitive load, or whatever you want to call it is lower on the front end.  Getting started.  But managing a lot of the changes in many locations becomes more difficult to orchestrate.&lt;/p&gt;
&lt;p&gt;This might be fine for small outfits or even startups, who&amp;rsquo;s whole business model is move fast while VCs more-or-less throw bologna at the wall to see what sticks.  Many of these companies will never have large scale infrastructure.  They don&amp;rsquo;t need to fully automate.  Or many of the people in that situation won&amp;rsquo;t be around to deal with the fallout.  They&amp;rsquo;re either software engineers white-knuckling it until an infra or DevOps person is hired, will have moved on to the next role, or the company will no longer exist&lt;/p&gt;
&lt;p&gt;I guess that might reflect why everything is geared towards quick initial lift at the moment.&lt;/p&gt;
&lt;h2 id=&#34;relieving-humans-of-monotonously-repetitive-work&#34;&gt;Relieving humans of monotonously repetitive work&lt;/h2&gt;
&lt;p&gt;Man, the number of PRs and ticky-tack little changes that have to be opened to accomplish anything at some organizations is astounding.  The copy-pasting of boilerplate. Or even worse, tracking down and modifying said boiler plate.  All dreadfully boring, all dreadfully common.  It&amp;rsquo;s all too easy to design a workflow that is convoluted.&lt;/p&gt;
&lt;h2 id=&#34;increased-human-freedom-to-do-other-things&#34;&gt;Increased human freedom to do other things&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s very common for a DevOps engineer to basically be a glorified operator.  Help tickets and slack messages to debug convoluted CI/CD pipelines, coordinating lots of repetitive changes that need to be merged in a specific order, tracking down old boiler plate that needs to be modified, and anything else mentioned above.  It&amp;rsquo;s a time sink, and these aren&amp;rsquo;t adding value to organizations.  Rather than manually orchestrate &amp;ldquo;automated&amp;rdquo; processes, these individuals would be better served solving other problems for the business.&lt;/p&gt;
&lt;h1 id=&#34;but-its-not-all-fire-and-brimstone&#34;&gt;But it&amp;rsquo;s not all fire and brimstone&lt;/h1&gt;
&lt;p&gt;It wasn&amp;rsquo;t my intent to come across all doom-and gloom and bad mouth a bunch of tools.  To clarify;  I like these tools.  I use them daily. I reach for them when designing new things.  In terms of automation as an industry, we&amp;rsquo;re at the highest point we&amp;rsquo;ve ever been. More people than ever have access to tools that help them automate tasks.  Not to mention there is a network effect of steering everyone towards the same path&lt;sup id=&#34;fnref:8&#34;&gt;&lt;a href=&#34;#fn:8&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;8&lt;/a&gt;&lt;/sup&gt;. Even if it&amp;rsquo;s not straightforward to achieve the levels of automation previous tools afforded, it&amp;rsquo;s not an apples-to-apples comparison.  More is possible now.&lt;/p&gt;
&lt;p&gt;I just wanted to say that &lt;em&gt;it can be better&lt;/em&gt;. Those of us that automated it then are automating it now and we have opinions.&lt;/p&gt;
&lt;p&gt;If you have an opinion on any of these tools, think I was completely off-base, have a correction, or would like to highlight additional tooling  I would love to hear from you.  contact at pallissard dot net&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;these are how salt&amp;rsquo;s states or puppet&amp;rsquo;s manifests are designed&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;Ok, you can actually do this with some of the terraform tools, but it&amp;rsquo;s not as straightforward.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;I&amp;rsquo;d actually been complaining about it for years before I heard the official term &amp;ldquo;DRY&amp;rdquo; mentioned.&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34;&gt;
&lt;p&gt;or is it &lt;a href=&#34;https://github.com/roboll/helmfile&#34;&gt;here&lt;/a&gt;?, &lt;a href=&#34;https://github.com/roboll/helmfile/issues/1826#issuecomment-1086111095&#34;&gt;it&amp;rsquo;s been unclear&lt;/a&gt; for a while, but it looks like &lt;a href=&#34;https://github.com/roboll/helmfile/issues/2148&#34;&gt;they got it sorted out&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34;&gt;
&lt;p&gt;And for a lot of things, this makes sense.&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34;&gt;
&lt;p&gt;I lied, actually I am, but I&amp;rsquo;m pragmatic.  Sometimes it just doesn&amp;rsquo;t make sense.&amp;#160;&lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:7&#34;&gt;
&lt;p&gt;Not to mention the fact that if you don&amp;rsquo;t have a clear picture you&amp;rsquo;re certainly not enforcing standards or infrastructure requirements with any sort of technical guard-rail.&amp;#160;&lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:8&#34;&gt;
&lt;p&gt;Yeah, this has a lot of downsides as well but I think it&amp;rsquo;s overwhelmingly a net positive.&amp;#160;&lt;a href=&#34;#fnref:8&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
	<item>
      <title>Cloud Network Design: Vendor Agnostic Network Config</title>
      <link>https://pallissard.net/2022/08/14/network_design_cloud_two/</link>
      <pubDate>2022-08-14</pubDate>
      <guid>https://pallissard.net/2022/08/14/network_design_cloud_two/</guid>
      <description>&lt;h1 id=&#34;taking-configuration-transformation-and-separation-a-step-further&#34;&gt;Taking configuration transformation and separation a step further&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;https://pallissard.net/2022/07/08/network_design_cloud_one/&#34;&gt;In a previous post&lt;/a&gt; I&amp;rsquo;d defined a simple VPC peering based network solution. There I highlighted how I prefer to separate declarative configuration from logic; defining the data in terms of how people interact with it on a day-to-day basis rather than how it&amp;rsquo;s used in code to interact with machines.&lt;/p&gt;
&lt;p&gt;In that post I&amp;rsquo;d mentioned how one could imagine defining abstract interfaces to handle the idiosyncrasies between vendors.  If you haven&amp;rsquo;t read that, feel free to skip over my network musings, but at least go back and familiarize yourself with the configuration format.&lt;/p&gt;
&lt;p&gt;If you want to see the source for this you can find it &lt;a href=&#34;https://pallissard.net/srv/network_design_cloud_two/cdktf.tar.zst&#34;&gt;here&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Since we&amp;rsquo;re already transforming our configuration to a more automation-friendly form there is no reason we can&amp;rsquo;t abstract the vendor specific details away. The interface for subnets and their definition could look like so.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;export interface SubnetData {}
export type SubnetDefinition&amp;lt;T extends SubnetData&amp;gt; = PartialRecord&amp;lt;
  Subnets,
  // Subnets are definied in our config.  This enforces valid names
  T
&amp;gt;;
export interface NetworkData&amp;lt;T&amp;gt; {
  subnets: SubnetDefinition&amp;lt;T&amp;gt;;
}

export type NetworkConfig&amp;lt;T, N extends NetworkData&amp;lt;T&amp;gt;&amp;gt; = Record&amp;lt;
  Vpcs,
  // Vpcs are definied in our config.  This enforces valid names
  N
&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Given this, we can extend the interfaces; ensuring that our datastructures between cloud vendors are identical while sweeping the vendor specific configuration under the rug.&lt;/p&gt;
&lt;h2 id=&#34;config-transformation-examples&#34;&gt;Config transformation examples&lt;/h2&gt;
&lt;p&gt;As we did previously, we&amp;rsquo;ll take a configuration that I like to work with directly and transform it to something easier to program with.  We&amp;rsquo;ll add/transform to the vendor specific options as we go.&lt;/p&gt;
&lt;h3 id=&#34;gcp-specific-interfaces&#34;&gt;GCP specific interfaces&lt;/h3&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;interface S extends SubnetData {
  config: {
    project: string;
    ipCidrRange: string;
    gatewayAddress: string;
  };
}
interface N&amp;lt;S&amp;gt; extends NetworkData&amp;lt;S&amp;gt; {
  project: string;
}

export class GcpNetworkingConfig implements NetworkDefinition {
  topo = topo;
  peers = peers;
  firewall = firewall;
  config: NetworkConfig&amp;lt;S, N&amp;lt;S&amp;gt;&amp;gt;;

  constructor(project: Project) {
    const l = getKeys(this.topo).map((j) =&amp;gt; {
      const subnets = this.topo[j].subNets;
      return {
        [j]: {
          project: project.name,
          subnets: getKeys(subnets)
            .map((k) =&amp;gt; {
              return {
                [k]: {
                  config: {
                    project: project.name,
                    ipCidrRange: subnets[k].cidr,
                    gatewayAddress: subnets[k].gateway,
                  },
                },
              } as SubnetDefinition&amp;lt;S&amp;gt;;
            })
            .reduce((obj, i) =&amp;gt; {
              return { ...obj, ...i };
            }),
        },
      } as NetworkConfig&amp;lt;S, N&amp;lt;S&amp;gt;&amp;gt;;
    });
    this.config = l.reduce((obj, i) =&amp;gt; {
      return { ...obj, ...i };
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;aws&#34;&gt;AWS&lt;/h3&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;interface S extends SubnetData {
  config: {
    cidrBlock: string;
  };
}

interface N&amp;lt;S&amp;gt; extends NetworkData&amp;lt;S&amp;gt; {
  cidrBlock: string;
}

export class AwsNetworkingConfig implements NetworkDefinition {
  topo = topo;
  peers = peers;
  firewall = firewall;
  config: NetworkConfig&amp;lt;S, N&amp;lt;S&amp;gt;&amp;gt;;

  constructor() {
    const l = getKeys(this.topo).map((j) =&amp;gt; {
      const subnets = this.topo[j].subNets;
      return {
        [j]: {
          cidrBlock: this.topo[j].cidr,
          subnets: getKeys(subnets)
            .map((k) =&amp;gt; {
              return {
                [k]: {
                  config: {
                    cidrBlock: subnets[k].cidr,
                  },
                },
              } as SubnetDefinition&amp;lt;S&amp;gt;;
            })
            .reduce((obj, i) =&amp;gt; {
              return { ...obj, ...i };
            }),
        },
      } as NetworkConfig&amp;lt;S, N&amp;lt;S&amp;gt;&amp;gt;;
    });
    this.config = l.reduce((obj, i) =&amp;gt; {
      return { ...obj, ...i };
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h1 id=&#34;the-implementation-portion&#34;&gt;The implementation portion&lt;/h1&gt;
&lt;p&gt;This still leaves a fair amount of duplication/boilerplate between the implementations as you can see.&lt;/p&gt;
&lt;h2 id=&#34;examples&#34;&gt;examples&lt;/h2&gt;
&lt;h3 id=&#34;aws-1&#34;&gt;AWS&lt;/h3&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;export class AwsNetwork extends Construct {
  readonly config: NetworkingConfig;
  readonly vpcs: Record&amp;lt;Vpcs, vpc.Vpc&amp;gt;;
  readonly subnets: Record&amp;lt;Vpcs, Record&amp;lt;Subnets, vpc.Subnet&amp;gt;&amp;gt;;
  readonly peers: PartialRecord&amp;lt;
    Vpcs,
    PartialRecord&amp;lt;Vpcs, vpc.VpcPeeringConnection&amp;gt;
  &amp;gt;;
  readonly firewalls: Record&amp;lt;
    Vpcs,
    Record&amp;lt;Direction, Record&amp;lt;string, vpc.NetworkAclRule[]&amp;gt;&amp;gt;
  &amp;gt;;

  constructor(
    scope: Construct,
    name: string,
    config: NetworkingConfig,
  ) {
    super(scope, name);
    this.config = config;
    this.vpcs = this.networks(config);
    this.subnets = this.subnetworks(config);
    this.peers = this.networkspeering(config);
    this.firewalls = this.firewall(config);
  }
[snip]
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;gcp&#34;&gt;GCP&lt;/h3&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;export class GcpNetwork extends Construct {
  readonly config: NetworkingConfig;
  readonly vpcs: Record&amp;lt;Vpcs, ComputeNetwork&amp;gt;;
  readonly subnets: Record&amp;lt;Vpcs, Record&amp;lt;Subnets, ComputeSubnetwork&amp;gt;&amp;gt;;
  readonly peers: PartialRecord&amp;lt;
    Vpcs,
    PartialRecord&amp;lt;Vpcs, ComputeNetworkPeering&amp;gt;
  &amp;gt;;
  readonly firewalls: Record&amp;lt;
    Vpcs,
    Record&amp;lt;Direction, Record&amp;lt;string, ComputeFirewall&amp;gt;&amp;gt;
  &amp;gt;;
  constructor(
    scope: Construct,
    name: string,
    config: NetworkingConfig,
  ) {
    super(scope, name);
    this.config = config;
    this.vpcs = this.networks(config);
    this.subnets = this.subnetworks(config);
    this.peers = this.networkspeering(config);
    this.firewalls = this.firewall(config);
  }
[snip]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We could implement a class that takes an abstract type instead of the actual constructs like &lt;code&gt;vpc.Subnets&lt;/code&gt;, &lt;code&gt;ComputeSubnet&lt;/code&gt;, etc.  This seems like it would be doable but would take at least&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The class itself would have to accept a function from the GCP/AWS implementation&lt;/li&gt;
&lt;li&gt;modifications to the type that describes our transformed configuration&lt;/li&gt;
&lt;li&gt;modifications to the constructors that generate the config&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;But at that point abstracting may cost more than some boilerplate.  I&amp;rsquo;ll think about it some more.  If you have thoughts I&amp;rsquo;d love to hear them.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://pallissard.net/srv/network_design_cloud_two/md5sum.txt&#34;&gt;md5sum&lt;/a&gt;, &lt;a href=&#34;https://pallissard.net/srv/network_design_cloud_two/cdktf.tar.zst.asc&#34;&gt;gpg signature&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
	<item>
      <title>Aws cli v2 installation garbage</title>
      <link>https://pallissard.net/2022/07/21/aws_cli_garbage/</link>
      <pubDate>2022-07-21</pubDate>
      <guid>https://pallissard.net/2022/07/21/aws_cli_garbage/</guid>
      <description>&lt;h2 id=&#34;broke-ass-way-of-shipping-software&#34;&gt;Broke-ass way of shipping software&lt;/h2&gt;
&lt;p&gt;I feel like the aws-cli team is pretty ignorant of standard conventions when it comes to shipping software to end users.  As usual, I have opinions.&lt;/p&gt;
&lt;h3 id=&#34;installer-fails-but-not-really&#34;&gt;installer fails but not really&lt;/h3&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt; ---&amp;gt; Running in 66a1029f66b9
/aws/dist/aws: error while loading shared libraries: libdl.so.2: cannot open shared object file: No such file or directory
You can now run: /usr/local/bin/aws --version
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Super cool, error but exiting zero.   We can see right in the installer script that they don&amp;rsquo;t check return codes on subshells.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;set_global_vars() {
  ROOT_INSTALL_DIR=${PARSED_INSTALL_DIR:-/usr/local/aws-cli}
  BIN_DIR=${PARSED_BIN_DIR:-/usr/local/bin}
  UPGRADE=${PARSED_UPGRADE:-no}

  EXE_NAME=&amp;#34;aws&amp;#34;
  COMPLETER_EXE_NAME=&amp;#34;aws_completer&amp;#34;
  INSTALLER_DIR=&amp;#34;$( cd &amp;#34;$( dirname &amp;#34;$0&amp;#34; )&amp;#34; &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 &amp;amp;&amp;amp; pwd )&amp;#34; 
  INSTALLER_DIST_DIR=&amp;#34;$INSTALLER_DIR/dist&amp;#34;
  INSTALLER_EXE=&amp;#34;$INSTALLER_DIST_DIR/$EXE_NAME&amp;#34;
  AWS_EXE_VERSION=$($INSTALLER_EXE --version | cut -d &amp;#39; &amp;#39; -f 1 | cut -d &amp;#39;/&amp;#39; -f 2)
[snip]
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;shipping-a-dynamically-linked-binary-wrapper&#34;&gt;shipping a dynamically linked binary wrapper&lt;/h3&gt;
&lt;p&gt;It&amp;rsquo;s a damned bit of python code so you get cross-platform for free with the notable exceptions of any c-extensions that have non-portable implementations.  Despite that they ship with a wrapper that is &lt;a href=&#34;https://en.wikipedia.org/wiki/Dynamic-link_library&#34;&gt;dynamically linked&lt;/a&gt;.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;~  file /aws/dist/aws
/aws/dist/aws: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=1
da3a1d77c7109ce6444919f4a15e7e6c63d02fa, stripped
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This means that the machine you run it on has to have the same shared libraries on as the machine that built it.  Requiring shared libraries in-and-of itself is no big deal, this is how operating systems have worked for years and years.  The main issue with this is that they expect glibc to be available.  This completely ignores one of the most popular container platforms &lt;a href=&#34;https://www.alpinelinux.org/&#34;&gt;alpine&lt;/a&gt;, which uses &lt;a href=&#34;https://musl.libc.org/&#34;&gt;muslc&lt;/a&gt;.  If you have an upstream image that uses alpine and you need to add the aws-cli to it you&amp;rsquo;re out of luck.  You have to either rebuild the upstream image with a glibc environment as the source, or futz about and get the awscli working in alpine.&lt;/p&gt;
&lt;p&gt;Why they couldn&amp;rsquo;t just &lt;a href=&#34;https://en.wikipedia.org/wiki/Static_library&#34;&gt;statically link&lt;/a&gt; this wrapper script is beyond me.  Many languages compile statically linked binaries by default&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. Better yet, if they had provided a source distribution of their code we could plumb in a patch at build time to make the cli work with musl.&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h2 id=&#34;making-this-go&#34;&gt;Making this go&lt;/h2&gt;
&lt;p&gt;There are numerous threads and pages on this topic but variants of this &lt;a href=&#34;https://stackoverflow.com/questions/60298619/awscli-version-2-on-alpine-linux&#34;&gt;SO answer&lt;/a&gt; seem to be the most popular.  I opted for the self-hosted, poor-mans way of building this.&lt;/p&gt;
&lt;p&gt;Remember that error above? &lt;code&gt;error while loading shared libraries: libdl.so.2: cannot open shared object file: No such file or directory&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;~  mkdir kludge
# docker build (Dockerfile below)
# a wild error appears
~  cp /usr/lib64/libdl.so.2 kludge/
~  docker build -t test .
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Wash, rinse, repeat for each shared library.&lt;/p&gt;
&lt;h3 id=&#34;it-wound-up-looking-like-this&#34;&gt;It wound up looking like this&lt;/h3&gt;
&lt;p&gt;The libraries.  Not to bad, took all of 2 minutes.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;~  ls kludge
ld-linux-x86-64.so.2  libc.so.6  libdl.so.2  libm.so.6  libpthread.so.0  librt.so.1  libutil.so.1  libz.so.1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And we&amp;rsquo;ll need a wrapper script so we can set LD_LIBRARY_PATH.  To inform the linker of the shared library location when the binary is evoked.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;~  cat bin/aws
#!/usr/bin/env bash

main(){
  export LD_LIBRARY_PATH=/lib64
  awscli &amp;#34;$@&amp;#34;
  exit $?
}

main &amp;#34;$@&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And we muck with the LD_LIBRARY_PATH during the install as well, taking care to unset it when done so our main application doesn&amp;rsquo;t run into a bunch of symbol-related issues itself.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;~  cat Dockerfile
from alpine
run apk add curl bash
run mkdir /lib64      # musl doesn&amp;#39;t use /lib64 by default
copy kludge/ /lib64/

run curl &amp;#34;https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip&amp;#34; -o &amp;#34;awscliv2.zip&amp;#34; &amp;amp;&amp;amp; \
		unzip awscliv2.zip &amp;amp;&amp;amp; \
		rm awscliv2.zip

env LD_LIBRARY_PATH=/lib64
run /aws/install &amp;amp;&amp;amp; mv /usr/local/bin/aws /usr/local/bin/awscli
copy bin/aws /usr/local/bin/aws
env LD_LIBRARY_PATH=
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;and-there-you-have-it&#34;&gt;and there you have it&lt;/h3&gt;
&lt;p&gt;One self-hosted kludgy solution.  Garbage in, garbage out.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;~  docker build -t test .
[snip]
~  docker run -it test aws --version
aws-cli/2.7.17 Python/3.9.11 Linux/5.18.9-arch1-1 exe/x86_64.alpine.3 prompt/off
&lt;/code&gt;&lt;/pre&gt;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Which is wasteful in terms of memory and disk, but portable.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;This is fairly common for alpine builds&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
	<item>
      <title>Cloud Network Design: VPC peering</title>
      <link>https://pallissard.net/2022/07/08/network_design_cloud_one/</link>
      <pubDate>2022-07-08</pubDate>
      <guid>https://pallissard.net/2022/07/08/network_design_cloud_one/</guid>
      <description>&lt;h1 id=&#34;design-considerations-in-the-physical-datacenter&#34;&gt;Design considerations in the physical datacenter&lt;/h1&gt;
&lt;p&gt;In the physical datacenters that I&amp;rsquo;ve worked in we often spear-headed large initiatives to improve east-west traffic.  Racks in the same row communicating with each other degrading performance in racks unaffiliated with the traffic in question; either due to hair-pinning or a distribution switch that just wasn&amp;rsquo;t up for the task.&lt;/p&gt;
&lt;p&gt;From a pure bandwidth/latency perspective it would be ideal if every node &lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; would have a direct connection to every other node.  If you set aside the scaling issues one might encounter with immense route tables, the sheer port count, and cable management headaches for a moment, this would be ideal;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Traffic is isolated to participating parties.  You can&amp;rsquo;t effect your neighbor.&lt;/li&gt;
&lt;li&gt;Bandwidth is high. Links aren&amp;rsquo;t shared.&lt;/li&gt;
&lt;li&gt;Latency is as low. There are no hops&lt;/li&gt;
&lt;li&gt;The network is robust.  If one path goes down only two nodes are affected. And even then, only a subset is affected.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since a we can&amp;rsquo;t cable every server to every server, we typically achieve some of the similar benefits that by moving to a spine and leaf architecture (a form clos networking) with &lt;a href=&#34;https://en.wikipedia.org/wiki/Equal-cost_multi-path_routing&#34;&gt;ECMP&lt;/a&gt;.  We get the following benefits;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Improving latency by reducing the number of hops&lt;/li&gt;
&lt;li&gt;Improving overall throughput by removing bottlenecks and leveraging active-active links&lt;/li&gt;
&lt;li&gt;Scaling is a simple operation, you add more switches.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;to-the-cloud&#34;&gt;To the cloud&lt;/h1&gt;
&lt;p&gt;Despite the fact that the cloud allows infrastructure to be stood up quickly with pre-existing automation tools, one still has to take into account the same design considerations of a physical data center.  Maybe your application is a simple crud app that only needs to speak to external customers and north-south is all you really care about.  But maybe you have an application that has a large persistence layer, or needs to share an inordinate amount of state.&lt;/p&gt;
&lt;p&gt;Since we&amp;rsquo;re in the cloud, switch ports and cables are no longer problems we have to deal with.  There are limits&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; to peering, particularly the number of peering connections.  The limits do set an upper bound, but even with an upper bound, connecting all of these traditionally would be still be tedious and a management headache.  Luckily for us we can automate this process with modern IaC tools and frameworks.  Meaning, we can get several of the aformentioned benefits with a very simple design and minimal upkeep.&lt;/p&gt;
&lt;h2 id=&#34;a-side-note-on-awss-transit-gateway&#34;&gt;A side note on AWS&amp;rsquo;s transit gateway&lt;/h2&gt;
&lt;p&gt;Back in 2018, AWS released the transit gateway which is generally speaking, a hub and spoke architecture.  While it has it&amp;rsquo;s use cases, and I&amp;rsquo;m sure many are bound to disagree with me, it seems like it&amp;rsquo;s a step back in a few ways.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;centralized routing and firewall rules&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.AWS.amazon.com/VPC/latest/tgw/transit-gateway-quotas.html#attachments-quotas&#34;&gt;throughput limitations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Not to mention, they did without it for what? over a decade?  GCP still doesn&amp;rsquo;t have a 1-to-1 offering like the transit gateway &lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;.  The TGW &lt;em&gt;feels&lt;/em&gt; like it was crafted for management via the AWS console.&lt;/p&gt;
&lt;p&gt;While transit gateways have a use-case and place.  VPC peering has a lot to offer, especially when paired with some custom automation.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Separating all of the configuration components such as routing and firewall rules
&lt;ul&gt;
&lt;li&gt;It&amp;rsquo;s much harder to make a site-wide mistake when managing separate rules from a central place&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Improving overall throughput&lt;/li&gt;
&lt;li&gt;With automation we avoid the headache of managing all of these peers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That said there are limitations;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We don&amp;rsquo;t have transitive peering, so we are forced to go all in on a mesh&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;As stated earlier the number of peers per VPC have an upper bound&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;building-a-solution&#34;&gt;Building a solution&lt;/h1&gt;
&lt;p&gt;I&amp;rsquo;ll step through a POC using cdktf and GCP.  If you want to see the source for this you can find it &lt;a href=&#34;https://pallissard.net/srv/network_design_cloud_one/cdktf.tar.zst&#34;&gt;here&lt;/a&gt;&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;While there is an initial lift in coding, particularly between transforming your configuration into the form useable by terraform, the day to day maintenance burden is lower due to a sane configuration in a central location.  The whole &lt;em&gt;get shit done&lt;/em&gt;, &lt;em&gt;MVP&lt;/em&gt;, mentality of work is often used by folks as an excuse to cut corners.&lt;/p&gt;
&lt;p&gt;Rather than quickly throw up something that with off the shelf automation tools with little regard for future flexibility; why not just come up with a simple, maintainable in-house design?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOT EVERYTHING HAS TO BE A COMMUNITY SUPPORTED MODULE&lt;/strong&gt;.  In fact I&amp;rsquo;d argue that for your foundational layers of infrastructure it can be worth your while to do some of the heavy lifting yourself.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You have a complete understanding of the dependencies&lt;/li&gt;
&lt;li&gt;You control all future changes&lt;/li&gt;
&lt;li&gt;You probably only need a subset of functionality so you can keep your implementation simple&lt;/li&gt;
&lt;li&gt;You aren&amp;rsquo;t at the mercy of any upstream changes.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;a-contrived-example&#34;&gt;A contrived example&lt;/h2&gt;
&lt;h3 id=&#34;defining-your-configuration-aka-a-schema&#34;&gt;Defining your configuration, aka a schema&lt;/h3&gt;
&lt;p&gt;Rather than think about how the tools available to you expect their format defined&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;, I prefer to think in terms of how it would be preferable to modify and manage.  Less &amp;ldquo;&lt;em&gt;how do I make this work&lt;/em&gt;&amp;rdquo; and more &amp;ldquo;&lt;em&gt;how do I want this to work&lt;/em&gt;&amp;rdquo;&lt;/p&gt;
&lt;h4 id=&#34;in-networks-i-think-in-terms-of-topographies&#34;&gt;In networks I think in terms of topographies;&lt;/h4&gt;
&lt;p&gt;And with CDKTF we can enforce an actual type on our configuration.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;type Cidr = string;
type Gateway = string;
type VpcAllocation = {
  subNets: Record&amp;lt;Subnets, { cidr: Cidr; gateway: Gateway }&amp;gt;;
};
type Topo = Record&amp;lt;Vpcs, VpcAllocation&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;readonly topo: Topo = {
  infra: {
    subNets: {
      app: {
        cidr: &amp;#39;10.92.0.0/22&amp;#39;,
        gateway: &amp;#39;10.92.0.1&amp;#39;,
      },
      db: {
        cidr: &amp;#39;10.92.4.0/24&amp;#39;,
        gateway: &amp;#39;10.92.4.1&amp;#39;,
      },
    },
  },
  management: {
    subNets: {
[snip]
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id=&#34;peers-i-think-of-mappings&#34;&gt;peers I think of mappings&lt;/h4&gt;
&lt;p&gt;Now, this looks like it could be cut down further as there are always two ends of a peering.  One would be tempted to specify the peering once.  But by specifying the peering on each end;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It&amp;rsquo;s easy to look up a given VPC and see what it&amp;rsquo;s peered with.&lt;/li&gt;
&lt;li&gt;We don&amp;rsquo;t have to come up with a policy/system for determining which network the peering is specified.  (ex. Alphabetical? what if we add a new network that upsets that mix?)&lt;/li&gt;
&lt;li&gt;There are technically two parts to a peer and we&amp;rsquo;re specify them both explicitly.  IMO this is easy to reason about.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;readonly peers: Peers = {
  management: [&amp;#39;infra&amp;#39;, &amp;#39;sales&amp;#39;],
  infra: [&amp;#39;management&amp;#39;],
  sales: [&amp;#39;management&amp;#39;],
};
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;and-in-firewall-rules-i-think-of-traditional-firewall-chains&#34;&gt;and in firewall rules I think of traditional firewall chains&lt;/h3&gt;
&lt;p&gt;In the firewall rules I am using terms that closely map to the GCP terraform provider, although I have added an additional &lt;code&gt;sourceNetworks&lt;/code&gt; field that I can match on to pull cidr blocks out of the live config.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;readonly firewall: Record&amp;lt;Vpcs, FwConfig&amp;gt; = {
  management: {
    ingress: [
      {
        name: &amp;#34;ping&amp;#34;,
        allow: true,
        priority: 1000,
        protocol: &amp;#39;icmp&amp;#39;,
        sourceNetworks: {infra: &amp;#39;app&amp;#39;} // here
      },
      {
        name: &amp;#34;catchall&amp;#34;,
        allow: false,
        priority: 10001,
        protocol: &amp;#39;all&amp;#39;,
        sourceRanges: [&amp;#34;0.0.0.0/0&amp;#34;]
      },

    ],
    egress: [
      {
        name: &amp;#34;ping&amp;#34;,
        allow: true,
        priority: 1000,
        protocol: &amp;#39;icmp&amp;#39;,
      },
      {
        name: &amp;#34;catchall&amp;#34;,
        allow: false,
        priority: 10001,
        protocol: &amp;#39;all&amp;#39;,
      },
    ],
  },
  infra: {
    egress: [
      {
        name: &amp;#34;ping&amp;#34;,
        allow: true,
[snip]
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;making-our-representation-useful&#34;&gt;making our representation useful.&lt;/h2&gt;
&lt;h3 id=&#34;types-to-leverage&#34;&gt;types to leverage&lt;/h3&gt;
&lt;p&gt;Now that we&amp;rsquo;ve defined the types that are easy to modify.&lt;/p&gt;
&lt;h4 id=&#34;we-can-define-the-type-that-is-easy-to-work-with-in-the-language&#34;&gt;We can define the type that is easy to work with in the language.&lt;/h4&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;export type SubnetConfig = PartialRecord&amp;lt;
  Subnets,
  { config: ComputeSubnetworkConfig }
&amp;gt;;

export type NetworkConfig = Record&amp;lt;
  Vpcs,
  {
    project: string;
    subnets: SubnetConfig;
  }
&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id=&#34;and-then-we-can-wrap-it-all-up-in-constructor&#34;&gt;And then we can wrap it all up in constructor.&lt;/h4&gt;
&lt;p&gt;Allowing us to have our cake and eat it too.  We have a type that&amp;rsquo;s easy to define, and a type that&amp;rsquo;s easy to work with.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;  constructor(project: Project) {
    const l = getKeys(this.topo).map((j) =&amp;gt; {
      const subnets = this.topo[j].subNets;
      return {
        [j as Vpcs]: {
          project: project.name,
          subnets: getKeys(subnets)
            .map((k) =&amp;gt; {
              return {
                [k]: {
                  config: {
                    project: project.name,
                    ipCidrRange: subnets[k].cidr,
                    gatewayAddress: subnets[k].gateway,
                  },
                },
              } as SubnetConfig;
            })
            .reduce((obj, i) =&amp;gt; {
              return { ...obj, ...i };
            }),
        },
      } as NetworkConfig;
    });
    this.config = l.reduce((obj, i) =&amp;gt; {
      return { ...obj, ...i };
    });
  }
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;using-the-representation&#34;&gt;using the representation&lt;/h2&gt;
&lt;p&gt;And now that our easy to maintain representation has been transformed to one that is a bit easier to program with all of our tasks become simple.  We can even stash the return types fields in the object iteslf for easy consumption by other modules.&lt;/p&gt;
&lt;p&gt;I won&amp;rsquo;t go on and on into the details of everything, if you want to see the nitty gritty see the source I linked to above.&lt;/p&gt;
&lt;h3 id=&#34;networks&#34;&gt;networks&lt;/h3&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;networks(i: GcpNetworkingConfig) {
  return getKeys(i.config)
    .map((v: Vpcs) =&amp;gt; {
      const rv = new ComputeNetwork(this, v, {
        name: v,
        project: i.config[v].project,
        autoCreateSubnetworks: false,
        deleteDefaultRoutesOnCreate: true,
      });
      return { [v]: rv } as Record&amp;lt;Vpcs, ComputeNetwork&amp;gt;;
    })
    .reduce((obj, i) =&amp;gt; {
      return { ...obj, ...i };
    });
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;and-subnets&#34;&gt;and subnets&lt;/h3&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;subnetworks(i: GcpNetworkingConfig) {
  return getKeys(i.config)
    .map((v: Vpcs) =&amp;gt; {
      return {
        [v]: getKeys(i.config[v].subnets)
          .map((s: Subnets) =&amp;gt; {
            const c = assert(i.config[v].subnets[s]).config;
            return {
              [s]: new ComputeSubnetwork(this, `${v}-${s}`, {
                ...c,
                name: `${v}-${s}`,
                network: this.vpcs[v].name,
              }),
            } as Record&amp;lt;Subnets, ComputeSubnetwork&amp;gt;;
          })
          .reduce((obj, i) =&amp;gt; {
            return { ...obj, ...i };
          }),
      } as Record&amp;lt;Vpcs, Record&amp;lt;Subnets, ComputeSubnetwork&amp;gt;&amp;gt;;
    })
    .reduce((obj, i) =&amp;gt; {
      return { ...obj, ...i };
    });
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;and-firewalls&#34;&gt;and firewalls&lt;/h3&gt;
&lt;p&gt;ok, admittedly this one&amp;rsquo;s a bit beefier and shows some of typescripts warts. &lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;firewall(i: GcpNetworkingConfig) {
  return getKeys(i.firewall)
    .map((v) =&amp;gt; {
      return getKeys(i.firewall[v])
        .map((direction) =&amp;gt; {
          const ad = (i: boolean): &amp;#39;allow&amp;#39; | &amp;#39;deny&amp;#39; =&amp;gt; {
            return i == true ? &amp;#39;allow&amp;#39; : &amp;#39;deny&amp;#39;;
          };
          return i.firewall[v][direction]
            .map((j) =&amp;gt; {
              const sourceRanges =
                j.sourceRanges != undefined ? j.sourceRanges : [];

              const sourceNetworks =
                j.sourceNetworks != undefined
                  ? getKeys(j.sourceNetworks).map((net) =&amp;gt; {
                      return this.subnets[net][
                        // typechecker ain&amp;#39;t that great
                        assert(assert(j.sourceNetworks)[net])
                      ].ipCidrRange;
                    })
                  : [];
              return {
                [v]: {
                  [direction]: {
                    [j.name]: new ComputeFirewall(
                      this,
                      `${v}-${direction}-${ad(j.allow)}-${j.name}`,
                      {
                        name: `${v}-${direction}-${j.name}`,
                        project: this.vpcs[v].project,
                        [ad(j.allow)]: {
                          protocol: j.protocol,
                          ports: j.ports,
                        },
                        priority: j.priority,
                        sourceTags: j.sourceTags,
                        sourceRanges: [
                          ...sourceRanges,
                          ...sourceNetworks,
                        ],
                        network: this.vpcs[v].id,
                        direction: direction.toUpperCase(),
                        logConfig: {
                          metadata: &amp;#39;INCLUDE_ALL_METADATA&amp;#39;,
                        },
                      },
                    ),
                  },
                },
              } as Record&amp;lt;
                Vpcs,
                Record&amp;lt;Direction, Record&amp;lt;string, ComputeFirewall&amp;gt;&amp;gt;
              &amp;gt;;
            })
            .reduce((obj, i) =&amp;gt; {
              return { ...obj, ...i };
            });
        })
        .reduce((obj, i) =&amp;gt; {
          return { ...obj, ...i };
        });
    })
    .reduce((obj, i) =&amp;gt; {
      return { ...obj, ...i };
    });
}
&lt;/code&gt;&lt;/pre&gt;&lt;h1 id=&#34;you-get-the-idea&#34;&gt;You get the idea&lt;/h1&gt;
&lt;p&gt;So yeah, peering is good and typescript is alright for this sort of thing.  It feels a &lt;em&gt;bit&lt;/em&gt; heavier than dealing with HCL when yout take into account the dev environment and initial setup. But dealing with it on a day to day basis is less painful despite the fact that the tools in TS/JS ecosystem &lt;a href=&#34;https://pallissard.net/2022/06/27/limiting_application_resources/&#34;&gt;are resource hogs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Additionally, I could imagine defining an abstract class with methods like &lt;code&gt;network&lt;/code&gt;, &lt;code&gt;firewall&lt;/code&gt;, etc that accept a config block and set up the proper constructs.  Then you could have a class for each cloud provider that implemented the methods, created the proper contstructs, and mapped the fields properly.  Keeping your config somewhat agnostic between the providers.   Naturally there&amp;rsquo;d be edge cases all over the place, but it&amp;rsquo;s a good thought exercise for practicing clean design.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;In this context, when I say node I mean server, appliance, anything.  Not network nodes in the strict sense.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;Currently it defaults to &lt;a href=&#34;https://docs.AWS.amazon.com/VPC/latest/userguide/amazon-VPC-limits.html#VPC-limits-peering&#34;&gt;50 for AWS&lt;/a&gt; and &lt;a href=&#34;https://cloud.google.com/VPC/docs/quota#VPC-peering&#34;&gt;25 for gcp&lt;/a&gt;.  Although those limits can be increased.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;Although it&amp;rsquo;s likely not needed since the way GCP segregates infrastructure by projects is a lot less heavy handed than how AWS namespaces via account.  The account is such a brutal namespace.&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34;&gt;
&lt;p&gt;Well not exactly, we&amp;rsquo;re not going to peer VPCs that don&amp;rsquo;t need to talk to each other.  Satisfying security requirements and helping  keep us under the VPC peering limits.&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://pallissard.net/srv/network_design_cloud_one/md5sum.txt&#34;&gt;md5sum&lt;/a&gt;, &lt;a href=&#34;https://pallissard.net/srv/network_design_cloud_one/cdktf.tar.zst.asc&#34;&gt;gpg signature&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34;&gt;
&lt;p&gt;Not that there isn&amp;rsquo;t at time and place to think of your output format&amp;#160;&lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:7&#34;&gt;
&lt;p&gt;While my approach could likely be improved, not being able to type narrow via ternary is quite a shame.&amp;#160;&lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
	<item>
      <title>Limiting application resources</title>
      <link>https://pallissard.net/2022/06/27/limiting_application_resources/</link>
      <pubDate>2022-06-27</pubDate>
      <guid>https://pallissard.net/2022/06/27/limiting_application_resources/</guid>
      <description>&lt;p&gt;I run a lot of hardware that&amp;rsquo;s well over a decade old.  Up until last year I exclusively used an HP USDT as my daily driver.  While it often works without issue, many &amp;ldquo;modern&amp;rdquo; applications are resource intensive.  Often leading to cpu starvation or memory contention.  In order to keep the desktop environment afloat in these situations I&amp;rsquo;ve been wrapping commands in cgroups using systemd to enforce memory limits on them.   Even though I have had a modern laptop for the past year, I still enforce these same limits&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.  I figured I&amp;rsquo;d share my &lt;del&gt;hacky&lt;/del&gt; convenient zsh aliases for performing such things.&lt;/p&gt;
&lt;p&gt;This isn&amp;rsquo;t so much an actual post as it is a dump of some aliases.&lt;/p&gt;
&lt;h2 id=&#34;dispatching-commands&#34;&gt;Dispatching commands&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m not a complete systemd fanboy, but one of the nice things is how process groups have a pair of abstractions layers, the slice and the unit, thrown on top of them.&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;  This lets you group commands into units, and units into slices.  You can carve up a slice as a category; imposing a limit on the resource sums for all units in said slice.  Then each unit can use individual resource limits.  If a unit hits its individual limit, it will be throttled, start swapping, or be killed individually.   But if the sum of all the units in a slice hit the slice limits, they will be penalized as a whole.&lt;/p&gt;
&lt;p&gt;User slices follow the following format&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;~  cat ~/.config/systemd/user/browsers.slice
[Unit]
Description=browsers
DefaultDependencies=no
Before=slices.target
Requires=system.slice
After=system.slice

[Slice]
MemoryHigh=10G
MemoryMax=11G
MemorySwapMax=1024M
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I wrap it in a dispatching function, mainly so I can plumb in changes centrally.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function dispatch {

	if [[ $USER == &amp;#34;root&amp;#34; ]]; then
		command &amp;#34;$binary&amp;#34; &amp;#34;$@&amp;#34;
		return $?
	fi

	declare args=(
		--user
		--same-dir
		-p IOAccounting=yes
		-p MemoryAccounting=yes
		-p TasksAccounting=yes
	)
	while (($#)); do
		case &amp;#34;$1&amp;#34; in
			-c)
				args+=&amp;#34;-p&amp;#34;
				args+=&amp;#34;CPUWeight=$2&amp;#34;; shift 2
				;;
			-mm)
				args+=&amp;#34;-p&amp;#34;
				args+=&amp;#34;MemoryMax=$2&amp;#34;; shift 2
				;;
			-mh)
				args+=&amp;#34;-p&amp;#34;
				args+=&amp;#34;MemoryHigh=$2&amp;#34;; shift 2
				;;
			-s)
				args+=&amp;#34;-p&amp;#34;
				args+=&amp;#34;MemorySwapMax=$2&amp;#34;; shift 2
				;;
			--scope)
				args+=--scope; shift
				;;
			--slice)
				args+=&amp;#34;--slice=$2&amp;#34;; shift 2
				;;
			--name)
				name=$1; shift 2
				;;
			-P)
				args+=-P; shift
				;;
			--binary)
				[ -z &amp;#34;$name&amp;#34; ] || name=$2
				binary=&amp;#34;$2&amp;#34;; shift
				;;
			*)
			break
		esac
	done
	systemd-run $args &amp;#34;$@&amp;#34;  2&amp;gt; &amp;gt;(&amp;gt;&amp;amp;2 grep -vE &amp;#39;Running.*as unit:&amp;#39;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, this works all well and good from a terminal. But a lot of processes call fork/execv and for those I drop a wrapper in a &lt;code&gt;~/bin&lt;/code&gt; directory that I&amp;rsquo;ve added to my path.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;~  cd ~/bin
~  cat .dispatch/template
#!/usr/bin/env zsh
# redirecting source to dev null is important when you&amp;#39;re parsing output
source ~/.zshrc &amp;gt;/dev/null 2&amp;gt;/dev/null 
&amp;#34;${ZSH_ARGZERO##*/}&amp;#34; $@
~  ln -s .dispatch/template firefox
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is a must if you want to wrap commands from your launcher or some of your lsp evoked commands. &lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;  That&amp;rsquo;s basically it for the mechanics of the thing.&lt;/p&gt;
&lt;h2 id=&#34;a-little-more-nuance&#34;&gt;A little more nuance&lt;/h2&gt;
&lt;p&gt;While I&amp;rsquo;m on the topic of lsp/development environments; so many commands use node that I wrap those separately.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function node {
	case &amp;#34;$1&amp;#34; in
		*&amp;#34;typescript-language-server&amp;#34;*)
			dispatch --name tsserver \
				--scope \
				--slice node \
				-c 3000 \
				-mh 3.5G \
				-mm 3.6G \
				-s 128M \
				--binary /usr/bin/node &amp;#34;$@&amp;#34;
			;;
		*&amp;#34;pyright&amp;#34;*)
			dispatch --name pyright \
				--scope \
				--slice pythonlsp.slice \
				-c 100 \
				-mh 2000M \
				-mm 2048M \
				-s 48M \
				--binary /usr/bin/node &amp;#34;$@&amp;#34;
			;;
		*&amp;#34;tsc&amp;#34;*)
			dispatch \
				--name tsc \
				--scope \
				--slice node \
				-c 135 \
				-mh 2000M \
				-mm 2048M \
				-s 24M \
				--binary /usr/bin/node &amp;#34;$@&amp;#34;
			;;
		*&amp;#34;cdktf&amp;#34;*)
			dispatch \
				--name cdktf \
				--scope \
				--slice node \
				-c 35 \
				-mh 3G \
				-mm 3G \
				-s 24M \
				--binary /usr/bin/node &amp;#34;$@&amp;#34;
			;;
		*&amp;#34;eslint_d&amp;#34;* | *&amp;#34;core_d&amp;#34;*)
			dispatch \
				--name eslint_d \
				--scope \
				--slice node \
				-c 200 \
				-mh 4.3G \
				-mm 4.5G \
				-s 24M \
				--binary /usr/bin/node &amp;#34;$@&amp;#34;
			;;
		*&amp;#34;eslint&amp;#34;*)
			dispatch \
				--name eslint \
				--scope \
				--slice node \
				-c 300 \
				-mh 2.5G \
				-mm 2.6G \
				-s 24M \
				--binary /usr/bin/node &amp;#34;$@&amp;#34;
			;;
		*&amp;#34;prettier-eslint&amp;#34;*)
			dispatch \
				--name prettier-eslint \
				--scope \
				--slice node \
				-c 300 \
				-mh 500M \
				-mm 512M \
				-s 12M \
				--binary /usr/bin/node &amp;#34;$@&amp;#34;
			;;
		*&amp;#34;prettier&amp;#34;*)
			dispatch \
				--name prettier \
				--scope \
				--slice node \
				-c 300 \
				-mh 500M \
				-mm 512M \
				-s 12M \
				--binary /usr/bin/node &amp;#34;$@&amp;#34;
			;;
		*&amp;#34;diagnostic-languageserver&amp;#34;*)
			dispatch \
				--name diagnostic-languageserver \
				--scope \
				--slice node \
				-c 90 \
				-mh 1024M \
				-mm 1024M \
				-s 12M \
				--binary /usr/bin/node &amp;#34;$@&amp;#34;
			;;
		*&amp;#34;npm&amp;#34;*)
			dispatch \
				--name npm \
				--scope \
				--slice node \
				-c 35 \
				-mh 1500M \
				-mm 1572M\
				-s 72M \
				--binary /usr/bin/node &amp;#34;$@&amp;#34;
			;;
		*)
			dispatch \
				--name node-generic \
				--scope \
				--slice node \
				-c 35 \
				-mh 500M \
				-mm 512M \
				-s 12M \
				--binary /usr/bin/node &amp;#34;$@&amp;#34;
			;;
	esac
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I match python similarly &lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;python () {
	if [ -n  &amp;#34;$VIRTUAL_ENV&amp;#34; ]; then
		source $VIRTUAL_ENV/bin/activate
	fi
	case &amp;#34;$1&amp;#34; in
		*&amp;#34;jedi&amp;#34;*)
			dispatch --name jedi-language-server \
				--scope \
				--slice pythonlsp.slice \
				-c 100 \
				-mh 2000M \
				-mm 2048M \
				-s 48M \
				--binary python &amp;#34;$@&amp;#34;
			;;
		*)
			dispatch \
				--name python-generic \
				--scope \
				--slice pythonlsp.slice \
				-c 35 \
				-mh 500M \
				-mm 512M \
				-s 12M \
				--binary python &amp;#34;$@&amp;#34;
			;;
	esac
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;introspection&#34;&gt;Introspection&lt;/h2&gt;
&lt;p&gt;You can check on the status of it with &lt;code&gt;systemctl --user status browsers.slice&lt;/code&gt;.  Example output is as follows.  Notice how there are two scope units nested under the slice.  The firefox and chrome pids are nested under their respective scope.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;● browsers.slice - browsers
     Loaded: loaded (/home/matt/.config/systemd/user/browsers.slice; static)
    Drop-In: /home/matt/.config/systemd/user.control/browsers.slice.d
             └─50-MemoryHigh.conf, 50-MemoryMax.conf
     Active: active since Wed 2022-06-22 05:54:52 AKDT; 5 days ago
      Until: Wed 2022-06-22 05:54:52 AKDT; 5 days ago
      Tasks: 753
     Memory: 3.5G (high: 10.1G max: 10.0G swap max: 1.0G available: 6.4G)
        CPU: 10h 24min 23.110s
     CGroup: /user.slice/user-10010.slice/user@10010.service/browsers.slice
             ├─run-rd666fcddb4584959b3e72ec7422f707f.scope
             │ ├─2707939 /usr/lib/firefox/firefox
             │ ├─2708074 /usr/lib/firefox/firefox -contentproc -parentBuildID 20220609170544 -prefsLen 9513 -prefMapSize 266077 -appDir /usr/lib/fir&amp;gt;
             │ ├─2708100 /usr/lib/firefox/firefox -contentproc -childID 1 -isForBrowser -prefsLen 9631 -prefMapSize 266077 -jsInitLen 277128 -parent&amp;gt;
             │ ├─2708143 /usr/lib/firefox/firefox -contentproc -childID 2 -isForBrowser -prefsLen 14640 -prefMapSize 266077 -jsInitLen 277128 -paren&amp;gt;
             │ ├─3369479 /usr/lib/firefox/firefox -contentproc -parentBuildID 20220609170544 -prefsLen 18146 -prefMapSize 266077 -appDir /usr/lib/fi&amp;gt;
             │ ├─3374574 /usr/lib/firefox/firefox -contentproc -childID 161 -isForBrowser -prefsLen 19504 -prefMapSize 266077 -jsInitLen 277128 -par&amp;gt;
             │ ├─3473440 /usr/lib/firefox/firefox -contentproc -childID 168 -isForBrowser -prefsLen 19504 -prefMapSize 266077 -jsInitLen 277128 -par&amp;gt;
             │ ├─3585007 /usr/lib/firefox/plugin-container /home/matt/.mozilla/firefox/i3w1cbew.default/gmp-widevinecdm/4.10.2449.0 2707939 true gmp&amp;gt;
             │ ├─3619513 /usr/lib/firefox/firefox -contentproc -childID 195 -isForBrowser -prefsLen 19504 -prefMapSize 266077 -jsInitLen 277128 -par&amp;gt;
             │ ├─3619611 /usr/lib/firefox/firefox -contentproc -childID 196 -isForBrowser -prefsLen 19504 -prefMapSize 266077 -jsInitLen 277128 -par&amp;gt;
             │ ├─3619778 /usr/lib/firefox/firefox -contentproc -childID 197 -isForBrowser -prefsLen 19504 -prefMapSize 266077 -jsInitLen 277128 -par&amp;gt;
             │ ├─3768700 /usr/lib/firefox/firefox -contentproc -childID 210 -isForBrowser -prefsLen 19504 -prefMapSize 266077 -jsInitLen 277128 -par&amp;gt;
             │ ├─3774713 /usr/lib/firefox/firefox -contentproc -childID 211 -isForBrowser -prefsLen 19504 -prefMapSize 266077 -jsInitLen 277128 -par&amp;gt;
             │ ├─3887801 /usr/lib/firefox/firefox -contentproc -childID 213 -isForBrowser -prefsLen 19504 -prefMapSize 266077 -jsInitLen 277128 -par&amp;gt;
             │ ├─3930854 /usr/lib/firefox/firefox -contentproc -childID 217 -isForBrowser -prefsLen 19504 -prefMapSize 266077 -jsInitLen 277128 -par&amp;gt;
             │ ├─3933460 /usr/lib/firefox/firefox -contentproc -childID 218 -isForBrowser -prefsLen 19504 -prefMapSize 266077 -jsInitLen 277128 -par&amp;gt;
             │ ├─3933865 /usr/lib/firefox/firefox -contentproc -childID 219 -isForBrowser -prefsLen 19504 -prefMapSize 266077 -jsInitLen 277128 -par&amp;gt;
             │ ├─3935821 /usr/lib/firefox/firefox -contentproc -childID 220 -isForBrowser -prefsLen 19504 -prefMapSize 266077 -jsInitLen 277128 -par&amp;gt;
             │ └─3936239 /usr/lib/firefox/firefox -contentproc -childID 221 -isForBrowser -prefsLen 19504 -prefMapSize 266077 -jsInitLen 277128 -par&amp;gt;
             └─run-re6a64a3f61e9425280a6cc758d955648.scope
               ├─3927358 &amp;#34;/usr/lib/chromium/chromium --enable-features=UseOzonePlatform --ozone-platform=wayland 
               ├─3927371 /usr/lib/chromium/chrome_crashpad_handler --monitor-self --monitor-self-annotation=ptype=crashpad-handler &amp;#34;--database=/home&amp;gt;
               ├─3927373 /usr/lib/chromium/chrome_crashpad_handler --no-periodic-tasks --monitor-self-annotation=ptype=crashpad-handler &amp;#34;--database=&amp;gt;
               ├─3927377 &amp;#34;/usr/lib/chromium/chromium --type=zygote --no-zygote-sandbox --enable-crashpad --crashpad-handler-pid=3927371 --enable-cra&amp;gt;
               ├─3927378 &amp;#34;/usr/lib/chromium/chromium --type=zygote --enable-crashpad --crashpad-handler-pid=3927371 --enable-crash-reporter=,Arch Li&amp;gt;
               ├─3927380 &amp;#34;/usr/lib/chromium/chromium --type=zygote --enable-crashpad --crashpad-handler-pid=3927371 --enable-crash-reporter=,Arch Li&amp;gt;
               ├─3927405 &amp;#34;/usr/lib/chromium/chromium --type=gpu-process --ozone-platform=wayland --enable-crashpad --crashpad-handler-pid=3927371 --&amp;gt;
               ├─3927406 &amp;#34;/usr/lib/chromium/chromium --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-t&amp;gt;
               ├─3927411 &amp;#34;/usr/lib/chromium/chromium --type=utility --utility-sub-type=storage.mojom.StorageService --lang=en-US --service-sandbox-t&amp;gt;
               ├─3927482 &amp;#34;/usr/lib/chromium/chromium --type=renderer --enable-crashpad --crashpad-handler-pid=3927371 --enable-crash-reporter=,Arch &amp;gt;
               ├─3927483 &amp;#34;/usr/lib/chromium/chromium --type=renderer --enable-crashpad --crashpad-handler-pid=3927371 --enable-crash-reporter=,Arch &amp;gt;
               └─3927558 &amp;#34;/usr/lib/chromium/chromium --type=renderer --enable-crashpad --crashpad-handler-pid=3927371 --enable-crash-reporter=,Arch &amp;gt;

Jun 22 05:54:52 matt-gen-laptop-p01 systemd[809]: Created slice browsers.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I use the brittle &lt;code&gt;peek&lt;/code&gt; alias often&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function peek {
	[ -n &amp;#34;$1&amp;#34; ] || bail 1 &amp;#34;specify a slice&amp;#34;
	fid=()
	while read unit; do
	fid+=r&amp;#34;$unit&amp;#34;
	done &amp;lt; &amp;lt;(systemctl --user status &amp;#34;$1&amp;#34; | grep scope | cut -f 2- -d r)
	systemctl --user status $fid 
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;which in turn relies on bail, but that isn&amp;rsquo;t a hard requirement&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function bail {
	if [ -z &amp;#34;$1&amp;#34; ]; then
		printf &amp;#34;no exit code returned\n&amp;#34;
		return 1
	elif [ &amp;#34;$1&amp;#34; -ne 0 ]; then
		 [[ -z &amp;#34;$2&amp;#34; ]] &amp;amp;&amp;amp; printf &amp;#34;failed\n&amp;#34; || printf &amp;#34;%s\n&amp;#34; &amp;#34;$2&amp;#34;
	fi
	return &amp;#34;$1&amp;#34;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A variant of peek &lt;code&gt;obcess&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function obcess {
  [ -n &amp;#34;$1&amp;#34; ] || bail 1 &amp;#34;specify a slice&amp;#34;
  watch &amp;#39;zsh -c &amp;#34;source /home/matt/.zshrc &amp;gt;/dev/null 2&amp;gt;/dev/null &amp;amp;&amp;amp; peek &amp;#39;$1&amp;#39;&amp;#34;&amp;#39;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Peek in action.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;~ peek browsers.slice
● run-rd666fcddb4584959b3e72ec7422f707f.scope - /usr/bin/firefox
     Loaded: loaded (/run/user/10010/systemd/transient/run-rd666fcddb4584959b3e72ec7422f707f.scope; transient)
  Transient: yes Active: active (running) since Thu 2022-06-23 21:13:14 AKDT; 3 days ago
      Tasks: 620 (limit: 33435)
     Memory: 2.6G (high: 6.3G max: 6.3G swap max: 12.0M available: 3.6G)
        CPU: 8h 10min 52.728s
     CGroup: /user.slice/user-10010.slice/user@10010.service/browsers.slice/run-rd666fcddb4584959b3e72ec7422f707f.scope
             ├─2707939 /usr/lib/firefox/firefox
             ├─2708074 /usr/lib/firefox/firefox -contentproc -parentBuildID 20220609170544 -prefsLen 9513 -prefMapSize 266077 -appDir /usr/lib/firef&amp;gt;
             ├─2708100 /usr/lib/firefox/firefox -contentproc -childID 1 -isForBrowser -prefsLen 9631 -prefMapSize 266077 -jsInitLen 277128 -parentBu&amp;gt;
             ├─2708143 /usr/lib/firefox/firefox -contentproc -childID 2 -isForBrowser -prefsLen 14640 -prefMapSize 266077 -jsInitLen 277128 -parentB&amp;gt;
             ├─3369479 /usr/lib/firefox/firefox -contentproc -parentBuildID 20220609170544 -prefsLen 18146 -prefMapSize 266077 -appDir /usr/lib/fire&amp;gt;
             ├─3374574 /usr/lib/firefox/firefox -contentproc -childID 161 -isForBrowser -prefsLen 19504 -prefMapSize 266077 -jsInitLen 277128 -paren&amp;gt;
             ├─3473440 /usr/lib/firefox/firefox -contentproc -childID 168 -isForBrowser -prefsLen 19504 -prefMapSize 266077 -jsInitLen 277128 -paren&amp;gt;
             ├─3585007 /usr/lib/firefox/plugin-container /home/matt/.mozilla/firefox/i3w1cbew.default/gmp-widevinecdm/4.10.2449.0 2707939 true gmplu&amp;gt;
             ├─3619513 /usr/lib/firefox/firefox -contentproc -childID 195 -isForBrowser -prefsLen 19504 -prefMapSize 266077 -jsInitLen 277128 -paren&amp;gt;
             ├─3619611 /usr/lib/firefox/firefox -contentproc -childID 196 -isForBrowser -prefsLen 19504 -prefMapSize 266077 -jsInitLen 277128 -paren&amp;gt;
             ├─3619778 /usr/lib/firefox/firefox -contentproc -childID 197 -isForBrowser -prefsLen 19504 -prefMapSize 266077 -jsInitLen 277128 -paren&amp;gt;
             ├─3768700 /usr/lib/firefox/firefox -contentproc -childID 210 -isForBrowser -prefsLen 19504 -prefMapSize 266077 -jsInitLen 277128 -paren&amp;gt;
             ├─3774713 /usr/lib/firefox/firefox -contentproc -childID 211 -isForBrowser -prefsLen 19504 -prefMapSize 266077 -jsInitLen 277128 -paren&amp;gt;
             ├─3887801 /usr/lib/firefox/firefox -contentproc -childID 213 -isForBrowser -prefsLen 19504 -prefMapSize 266077 -jsInitLen 277128 -paren&amp;gt;
             ├─3930854 /usr/lib/firefox/firefox -contentproc -childID 217 -isForBrowser -prefsLen 19504 -prefMapSize 266077 -jsInitLen 277128 -paren&amp;gt;
             ├─3933460 /usr/lib/firefox/firefox -contentproc -childID 218 -isForBrowser -prefsLen 19504 -prefMapSize 266077 -jsInitLen 277128 -paren&amp;gt;
             ├─3933865 /usr/lib/firefox/firefox -contentproc -childID 219 -isForBrowser -prefsLen 19504 -prefMapSize 266077 -jsInitLen 277128 -paren&amp;gt;
             ├─3935821 /usr/lib/firefox/firefox -contentproc -childID 220 -isForBrowser -prefsLen 19504 -prefMapSize 266077 -jsInitLen 277128 -paren&amp;gt;
             └─3936239 /usr/lib/firefox/firefox -contentproc -childID 221 -isForBrowser -prefsLen 19504 -prefMapSize 266077 -jsInitLen 277128 -paren&amp;gt;

Jun 23 21:13:14 matt-gen-laptop-p01 systemd[809]: Started /usr/bin/firefox.

● run-re6a64a3f61e9425280a6cc758d955648.scope - /usr/bin/chromium
     Loaded: loaded (/run/user/10010/systemd/transient/run-re6a64a3f61e9425280a6cc758d955648.scope; transient)
  Transient: yes
     Active: active (running) since Mon 2022-06-27 16:09:22 AKDT; 12min ago
      Tasks: 123 (limit: 33435)
     Memory: 506.6M (high: 3.4G max: 3.4G swap max: 12.0M available: 2.9G)
        CPU: 3.823s
     CGroup: /user.slice/user-10010.slice/user@10010.service/browsers.slice/run-re6a64a3f61e9425280a6cc758d955648.scope
             ├─3927358 &amp;#34;/usr/lib/chromium/chromium --enable-features=UseOzonePlatform --ozone-platform=wayland 
             ├─3927371 /usr/lib/chromium/chrome_crashpad_handler --monitor-self --monitor-self-annotation=ptype=crashpad-handler &amp;#34;--database=/home/m&amp;gt;
             ├─3927373 /usr/lib/chromium/chrome_crashpad_handler --no-periodic-tasks --monitor-self-annotation=ptype=crashpad-handler &amp;#34;--database=/h&amp;gt;
             ├─3927377 &amp;#34;/usr/lib/chromium/chromium --type=zygote --no-zygote-sandbox --enable-crashpad --crashpad-handler-pid=3927371 --enable-crash&amp;gt;
             ├─3927378 &amp;#34;/usr/lib/chromium/chromium --type=zygote --enable-crashpad --crashpad-handler-pid=3927371 --enable-crash-reporter=,Arch Linu&amp;gt;
             ├─3927380 &amp;#34;/usr/lib/chromium/chromium --type=zygote --enable-crashpad --crashpad-handler-pid=3927371 --enable-crash-reporter=,Arch Linu&amp;gt;
             ├─3927405 &amp;#34;/usr/lib/chromium/chromium --type=gpu-process --ozone-platform=wayland --enable-crashpad --crashpad-handler-pid=3927371 --en&amp;gt;
             ├─3927406 &amp;#34;/usr/lib/chromium/chromium --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-typ&amp;gt;
             ├─3927411 &amp;#34;/usr/lib/chromium/chromium --type=utility --utility-sub-type=storage.mojom.StorageService --lang=en-US --service-sandbox-typ&amp;gt;
             ├─3927482 &amp;#34;/usr/lib/chromium/chromium --type=renderer --enable-crashpad --crashpad-handler-pid=3927371 --enable-crash-reporter=,Arch Li&amp;gt;
             ├─3927483 &amp;#34;/usr/lib/chromium/chromium --type=renderer --enable-crashpad --crashpad-handler-pid=3927371 --enable-crash-reporter=,Arch Li&amp;gt;
             └─3927558 &amp;#34;/usr/lib/chromium/chromium --type=renderer --enable-crashpad --crashpad-handler-pid=3927371 --enable-crash-reporter=,Arch Li&amp;gt;

Jun 27 16:
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;sundries&#34;&gt;Sundries&lt;/h2&gt;
&lt;h3 id=&#34;thoughts-on-web-browsers&#34;&gt;thoughts on web browsers&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;d really like to see support in firefox for their tab categories. Each tab category can be restricted by a slice or unit.  A clean implementation would probably require firefox speaking to systemd via dbus.&lt;/p&gt;
&lt;h3 id=&#34;wrapping-one-off-commands&#34;&gt;wrapping one-off commands&lt;/h3&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function wrap {
	limit=$1; shift;
	dispatch --name $1 \
		--scope \
		--slice wrap.slice \
		-c 50 \
		-mh $limit \
		-mm $limit \
		-s 1\
		--binary &amp;#34;$@&amp;#34;
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;killing-everything-in-a-slice&#34;&gt;killing everything in a slice&lt;/h3&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;systemctl --user stop node.slice
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;checking-for-failed-commands&#34;&gt;checking for failed commands&lt;/h3&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;systemctl --user failed
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;check-all-of-the-user-processes-and-their-slices&#34;&gt;check all of the user processes and their slices&lt;/h3&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;systemctl --user status
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;some-of-the-other-slices-i-use&#34;&gt;some of the other slices I use&lt;/h3&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;~  ls ~/.config/systemd/user/*.slice
/home/matt/.config/systemd/user/browsers.slice
/home/matt/.config/systemd/user/nvim.slice
/home/matt/.config/systemd/user/tmux.slice
/home/matt/.config/systemd/user/compilers.slice
/home/matt/.config/systemd/user/ocamllsp.slice
/home/matt/.config/systemd/user/vpn.slice
/home/matt/.config/systemd/user/gopls.slice
/home/matt/.config/systemd/user/pythonlsp.slice
/home/matt/.config/systemd/user/wrap.slice
/home/matt/.config/systemd/user/node.slice
/home/matt/.config/systemd/user/shell.slice
&lt;/code&gt;&lt;/pre&gt;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Well except for browsers and node, those get a slightly higher limit on a newer machine.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;I&amp;rsquo;ve spent all of my career dealing with posix environments and a good chunk in HPC.  I have to say that process groups could have been fleshed out a little more fully.  That fact that abstraction layers or additional automation are needed to properly manage children is a testament to that.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;Now why does a web browser can require that much RAM?&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/microsoft/TypeScript/issues/46028&#34;&gt;I&amp;rsquo;m looking at you tsc.&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34;&gt;
&lt;p&gt;I&amp;rsquo;m going to skip the disaster that is how I manage my virtual environments for now.&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
	<item>
      <title>Don&#39;t trust. Verify.</title>
      <link>https://pallissard.net/2022/06/14/dont_trust_verify/</link>
      <pubDate>2022-06-14</pubDate>
      <guid>https://pallissard.net/2022/06/14/dont_trust_verify/</guid>
      <description>&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt; I was disappointed to find out that typescript isn&amp;rsquo;t very strongly typed.  You still have to keep an eye out for &lt;code&gt;any&lt;/code&gt;, the generic object, and type coercion.&lt;/p&gt;
&lt;p&gt;At one of my more recent gigs, I was thrown into a Typescript environment via &lt;a href=&#34;https://www.terraform.io/cdktf&#34;&gt;cdktf&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.  Having dabbled in OCaml&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; and Haskell previously I was looking forward to having a strong, statically typed language to define infrastructure with.  Two of my biggest gripes with many configuration management and IaC solutions are a lack of schema enforcement and the heavy use of templating. &lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Having a full type system allows for some really nice things like this.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;type domains = &amp;#39;pallissard.net&amp;#39; | &amp;#39;matt.pallissard.net&amp;#39;;
export type validEmail = `${string}@${domains}`;

export interface UserConfig {
  email: validEmail;
  firstName: string;
  lastName: string;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This affords one the opportunity to;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Separate their code from their configuration&lt;/li&gt;
&lt;li&gt;Use the type system as a DSL of sorts for declaring configuration.  Handling schema enforcement at build time.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Exactly what I would hope for from a language with a strong type system.  However, I immediately ran into an eyebrow raiser.  This compiles.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;const parsed = JSON.parse(&amp;#39;[1]&amp;#39;)
const bar = 1 + parsed
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In fact, not only does it compile. Type coercion trumps annotations.  You can see with with the string representation of &lt;code&gt;11&lt;/code&gt; in the output below.  The annotations feel closer to type hints than actual annotations.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;const parsed: number = JSON.parse(&amp;#39;[1]&amp;#39;)
const bar: number = 1 + parsed
const baz: number = parsed[0] + 1
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;11 2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We&amp;rsquo;re effectively seeing the Javascript bleed through with the &lt;code&gt;any&lt;/code&gt; and the type coercion.  I&amp;rsquo;m shocked&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt; that for how popular this language is, and for how vocal the proselytizers are, the type-checker isn&amp;rsquo;t catching these things.  Things that people say you should use Typescript for.  This is supposed to be it&amp;rsquo;s core competency, turning runtime errors into compile time errors. &lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Interestingly enough, if we change our example a little bit.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;const parsed: number = JSON.parse(&amp;#39;{&amp;#34;k&amp;#34;: 1}&amp;#39;)
console.log(parsed + 1, parsed.k + 1)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We do receive compile time error, but it&amp;rsquo;s for a reference to the field &lt;code&gt;k&lt;/code&gt;.  Not for the incompatible addition operation &lt;code&gt;parsed + 1&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;
foo.ts:2:32 - error TS2339: Property &amp;#39;k&amp;#39; does not exist on type &amp;#39;number&amp;#39;.

2 console.log(parsed + 1, parsed.k + 1)
                                 ~


Found 1 error.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But if we modify it a bit more, we once again escape the compiler errors&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;const parsed: number = JSON.parse(&amp;#39;{&amp;#34;k&amp;#34;: 1}&amp;#39;)
console.log(parsed + 1, parsed[&amp;#34;k&amp;#34;] + 1)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I&amp;rsquo;m not saying typescript is all bad.&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;.  Sometimes you&amp;rsquo;re stuck needing to write something in-browser, or the tool you need is written in javascript.  But if one is reaching for typescript just for the type system there are certainly better options out there.&lt;/p&gt;
&lt;h2 id=&#34;examples&#34;&gt;Examples&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve had it pointed out by some that have grown up in javascript and have &lt;del&gt;Stockholm syndrome&lt;/del&gt; never been exposed to other languages and paradigms, that &amp;ldquo;Typescript &lt;em&gt;transpiles&lt;/em&gt; to Javascript and Javascript doesn&amp;rsquo;t have types so there are limitations&amp;rdquo;. &lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt; Do you know what also doesn&amp;rsquo;t have types? Machine code.  You know what a whole gang of statically typed languages use as a compilation target? Machine code.&lt;/p&gt;
&lt;p&gt;Here are some examples.  Not only do we get compilation errors, for some it feels awkward to write them incorrectly due to the type annotations and how the language generally operates.&lt;/p&gt;
&lt;h3 id=&#34;ocaml&#34;&gt;OCaml&lt;/h3&gt;
&lt;p&gt;If we try to do the same operation in OCaml&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;let () =
  let open Yojson.Basic.Util in
  let foo = Yojson.Basic.from_string &amp;#34;{\&amp;#34;k\&amp;#34;: 1}&amp;#34; in
  print_int (foo + 1)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We get a compile time error instead of a runtime error&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;4 |   print_int (foo + 1)
                 ^^^
Error: This expression has type Yojson.Basic.t
       but an expression was expected of type int
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But if we explicitly unpack and cast..&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;let () =
  (* no error handling *)
  let open Yojson.Basic.Util in
  let foo = Yojson.Basic.from_string &amp;#34;{\&amp;#34;k\&amp;#34;: 1}&amp;#34; in
  print_int (
    to_int(
      member &amp;#34;k&amp;#34; foo
    )+1
  );
  print_newline ();
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;we get&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;2
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;haskell&#34;&gt;Haskell&lt;/h3&gt;
&lt;p&gt;Again, like the typescript example attempts.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Main(main) where

import Prelude.Compat
import Data.Aeson
import Lib
import Control.Applicative (empty)

data Foo = Foo { k :: Integer }
        deriving (Show)

instance FromJSON Foo where
    parseJSON ( Object v )  = Foo &amp;lt;$&amp;gt;
                              v .: &amp;#34;k&amp;#34;
    parseJSON _             = empty


main :: IO ()
main = do
    let bar = decode &amp;#34;{\&amp;#34;k\&amp;#34;:1}&amp;#34; :: Maybe Foo
    bar+1 {- Specifically this line -}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Unsurprisingly, a compile time error&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;    • Couldn&amp;#39;t match type ‘Maybe’ with ‘IO’
      Expected: IO Foo
        Actual: Maybe Foo
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;but if we swap &lt;code&gt;bar+1&lt;/code&gt; out with some pattern matching&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;    case bar of
        Just(Foo(i)) -&amp;gt; print (i+1)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;we get the expected output&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;2
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;java&#34;&gt;java&lt;/h3&gt;
&lt;p&gt;Java&amp;rsquo;s a little different than the above.  It&amp;rsquo;s similar to how you should actually handle this in typescript.  You create an object, and instantiate an instance of your object with the json object.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;import com.google.gson.Gson;

public class foo {
  private class Bar {
    public Integer k;
    public Bar(Integer i){
      this.k = i;
    }
  }
  public static void main(String[] args) {
    Gson json = new Gson();
    Bar bar = json.fromJson(&amp;#34;{\&amp;#34;k\&amp;#34;: 1}&amp;#34;, Bar.class);
    System.out.println(bar+1);
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But that said, it still catches this, where typescript would not.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;foo.java:13: error: bad operand types for binary operator &amp;#39;+&amp;#39;
    System.out.println(bar+1);
                          ^
  first type:  foo.Bar
  second type: int
1 error
&lt;/code&gt;&lt;/pre&gt;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Expect a separate post on that later.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://discuss.OCaml.org/t/multicore-OCaml-dec-2020-jan-2021/7225&#34;&gt;https://discuss.OCaml.org/t/multicore-OCaml-dec-2020-jan-2021/7225&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;I&amp;rsquo;m still puzzled as to why the C preprocessor is evil while go templating will save us all.&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34;&gt;
&lt;p&gt;Not really, &lt;code&gt;popular != (good | sane)&lt;/code&gt;&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34;&gt;
&lt;p&gt;&lt;em&gt;technically&lt;/em&gt; we didn&amp;rsquo;t even get a runtime error.&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34;&gt;
&lt;p&gt;I&amp;rsquo;m just saying that javascript and the entirety of it&amp;rsquo;s ecosystem is.&amp;#160;&lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:7&#34;&gt;
&lt;p&gt;I kid you not.&amp;#160;&lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
  </channel>
</rss>
