<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Roman Geraskin</title>
    <description>The latest articles on DEV Community by Roman Geraskin (@rgeraskin).</description>
    <link>https://dev.to/rgeraskin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F403691%2Fe7ede7cc-e741-4505-8d57-c3f8fdd95e17.jpeg</url>
      <title>DEV Community: Roman Geraskin</title>
      <link>https://dev.to/rgeraskin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rgeraskin"/>
    <language>en</language>
    <item>
      <title>Infrastructure testing in practice</title>
      <dc:creator>Roman Geraskin</dc:creator>
      <pubDate>Tue, 30 Sep 2025 13:13:50 +0000</pubDate>
      <link>https://dev.to/rgeraskin/infrastructure-testing-in-practice-1jpb</link>
      <guid>https://dev.to/rgeraskin/infrastructure-testing-in-practice-1jpb</guid>
      <description>&lt;p&gt;Modern infrastructure moves fast. Configs change, components get upgraded, and small tweaks can have big ripple effects. Are you sure everything still works after each change?&lt;/p&gt;

&lt;p&gt;That’s where infrastructure auto tests shine: they validate real behavior in a real cluster and act as living specs for your platform.&lt;/p&gt;

&lt;p&gt;In this post, we’ll walk through a practical example: a test that validates Kubernetes Cluster Autoscaler by forcing a scale-up and checking end-to-end results.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we test: Cluster Autoscaler
&lt;/h2&gt;

&lt;p&gt;This is a simple example to show the approach. The goal: prove that writing infra tests is straightforward and genuinely useful.&lt;/p&gt;

&lt;p&gt;Cluster Autoscaler watches pending pods and scales the right node group when capacity is tight.&lt;/p&gt;

&lt;p&gt;Why test it? What can go wrong?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A bad autoscaler release.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Helm chart breaking change.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Misconfiguration.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Incompatible cluster components.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Access/quotas/IAM issues that block scale-up.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Networking issues (for external autoscalers).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our test covers the full path end-to-end:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Pending pods ──&amp;gt; Cluster Autoscaler ──&amp;gt; Node group scales ──&amp;gt; Pods Ready&lt;/code&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Detect the target node group for the current cluster context.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a deployment with replicas set to &lt;code&gt;current_nodes + 1&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use pod anti-affinity, so pods spread across nodes, guaranteeing the need for an extra node.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use node affinity/selector, so pods land only on the intended node group.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Verify we see the expected pending pod initially; then wait for all pods to be &lt;code&gt;Ready&lt;/code&gt; within a timeout.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Assert success when all pods are scheduled and ready.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How the test is designed
&lt;/h2&gt;

&lt;p&gt;I wrote the test in Python because it’s easy to read, most engineers already know it, and it’s usually available by default. No extra Python deps needed.&lt;/p&gt;

&lt;p&gt;If you prefer Bash, check out the &lt;a href="https://github.com/bats-core/bats-core" rel="noopener noreferrer"&gt;bats framework&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Node group detection&lt;/strong&gt;: EKS clusters use label &lt;code&gt;eks.amazonaws.com/nodegroup&lt;/code&gt; and expect group &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Workload shape&lt;/strong&gt;: A tiny deployment using &lt;code&gt;registry.k8s.io/pause:3.9&lt;/code&gt; with small CPU/memory requests. We add strict scheduling constraints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pod anti-affinity (&lt;code&gt;requiredDuringSchedulingIgnoredDuringExecution&lt;/code&gt;) to force one pod per node.&lt;/li&gt;
&lt;li&gt;Node selector/affinity so we only use the target node group.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Assertions&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Immediately after creation, we expect exactly one pod to be &lt;code&gt;Pending&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Within the timeout (5 minutes by default), all pods must become &lt;code&gt;Ready&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Running the test
&lt;/h2&gt;

&lt;p&gt;I use &lt;code&gt;mise&lt;/code&gt; to run the test. It’s a handy tool to manage dependencies and tasks, so teammates don’t need to learn a custom Python CLI.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;mise tasks&lt;/code&gt; to see what’s available. For the autoscaler test: &lt;code&gt;mise test:cluster-autoscaler&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It’s easy to drop into CI: add &lt;code&gt;kubectl&lt;/code&gt; and &lt;code&gt;python&lt;/code&gt; to &lt;code&gt;.mise.toml&lt;/code&gt; and run &lt;code&gt;mise install&lt;/code&gt; up front. More details in my post about &lt;a href="https://blog.rgeraskin.dev/dev-env-with-mise" rel="noopener noreferrer"&gt;mise&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code examples
&lt;/h2&gt;

&lt;p&gt;Below are minimal snippets from the real test suite. Full code is in the &lt;a href="https://github.com/rgeraskin/infra-tests" rel="noopener noreferrer"&gt;repo&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# cluster_autoscaler/test.py
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseInfraTest&lt;/span&gt;
&lt;span class="c1"&gt;# other imports
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ClusterAutoscaler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseInfraTest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;RESOURCE_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;infra-tests-cluster-autoscaler&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;TIMEOUT_SECONDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;  &lt;span class="c1"&gt;# 5 minutes
&lt;/span&gt;    &lt;span class="n"&gt;DEPLOYMENT_CHECK_DELAY_SECONDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# prepare common things that are used in all tests
&lt;/span&gt;        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;# set internal variables
&lt;/span&gt;        &lt;span class="c1"&gt;# ...
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_get_target_nodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Get all nodes and filter for target nodegroup
&lt;/span&gt;        &lt;span class="c1"&gt;# ...
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_cluster_autoscaler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Get current nodes in target node group
&lt;/span&gt;        &lt;span class="n"&gt;target_nodes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nodegroup_label_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_get_target_nodes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;current_node_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target_nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Calculate required replicas (current nodes + extra to trigger scaling)
&lt;/span&gt;        &lt;span class="n"&gt;required_replicas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current_node_count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

        &lt;span class="c1"&gt;# Read and prepare deployment manifest
&lt;/span&gt;        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deployment_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;manifest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;# Apply deployment manifest
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kubectl --context &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_context&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; apply -f -&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;manifest&lt;/span&gt;
            &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RESOURCE_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# deployment name
&lt;/span&gt;                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# namespace
&lt;/span&gt;                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RESOURCE_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# app label
&lt;/span&gt;                &lt;span class="n"&gt;required_replicas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# replicas count
&lt;/span&gt;                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RESOURCE_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# selector matchLabels app
&lt;/span&gt;                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RESOURCE_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# pod template app label
&lt;/span&gt;                &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nodegroup_label_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;nodegroup_label_value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# nodeSelector
&lt;/span&gt;                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RESOURCE_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# pod anti-affinity
&lt;/span&gt;            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Give k8s a moment to process the deployment
&lt;/span&gt;        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEPLOYMENT_CHECK_DELAY_SECONDS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Check that there is 1 pod in pending state
&lt;/span&gt;        &lt;span class="n"&gt;pods_cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kubectl --context &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_context&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; --namespace &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; get pods&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; -l app=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RESOURCE_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; -o jsonpath=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{{.items[*].status.phase}}&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;pod_phases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pods_cmd&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;pod_phases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pending&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Expected 1 pod in pending state, got &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pod_phases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Pending&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Wait for all pods to be ready using kubectl wait
&lt;/span&gt;        &lt;span class="n"&gt;wait_cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kubectl --context &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_context&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; --namespace &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; wait pod&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; -l app=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RESOURCE_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; --for=condition=Ready&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; --timeout=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TIMEOUT_SECONDS&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wait_cmd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mise configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# .mise.toml&lt;/span&gt;
&lt;span class="nn"&gt;[tools]&lt;/span&gt;
&lt;span class="py"&gt;python&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"3.13.0"&lt;/span&gt;
&lt;span class="py"&gt;kubectl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.31.1"&lt;/span&gt;

&lt;span class="nn"&gt;[tasks.test]&lt;/span&gt;
&lt;span class="py"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Run all infrastructure tests"&lt;/span&gt;
&lt;span class="py"&gt;run&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"python -m unittest discover -v"&lt;/span&gt;

&lt;span class="nn"&gt;[tasks."test:cluster-autoscaler"]&lt;/span&gt;
&lt;span class="py"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Run tests for cluster-autoscaler"&lt;/span&gt;
&lt;span class="py"&gt;run&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
python -m unittest -v cluster_autoscaler.test.ClusterAutoscaler
"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;deployment.yaml&lt;/code&gt; is a regular Kubernetes Deployment with &lt;code&gt;%s&lt;/code&gt; placeholders that get templated at runtime. A full example is &lt;a href="https://github.com/rgeraskin/blog-infra-tests" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Principles of solid infra tests
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simple&lt;/strong&gt;: easy to maintain and add new.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Small&lt;/strong&gt;: extract boilerplate into shared utilities.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fast&lt;/strong&gt;: speed matters.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Wait on conditions&lt;/strong&gt;: wait for states, not fixed sleeps.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Well-structured tests make it easy to add new ones — even with help from LLMs. Write the test case, then ask to implement it using the existing ones as a reference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;Infrastructure tests are your early-warning system for platform regressions. By validating real behavior in real clusters, you turn assumptions into executable specs.&lt;/p&gt;

&lt;p&gt;The Cluster Autoscaler test is a concise, low-risk example that catches issues you might otherwise notice only after a late-night surprise.&lt;/p&gt;

&lt;p&gt;Apply the same approach to other components to keep changes safe and verified.&lt;/p&gt;

</description>
      <category>python</category>
      <category>kubernetes</category>
      <category>devops</category>
      <category>iac</category>
    </item>
    <item>
      <title>Introducing GoDiffYAML Tool 💪</title>
      <dc:creator>Roman Geraskin</dc:creator>
      <pubDate>Tue, 30 Sep 2025 12:54:24 +0000</pubDate>
      <link>https://dev.to/rgeraskin/introducing-godiffyaml-tool-5d7i</link>
      <guid>https://dev.to/rgeraskin/introducing-godiffyaml-tool-5d7i</guid>
      <description>&lt;h2&gt;
  
  
  The YAML Diffing Dilemma
&lt;/h2&gt;

&lt;p&gt;YAML files are everywhere in software development — think Kubernetes manifests, Ansible playbooks, or any configuration management system. But when these files contain multiple documents (separated by &lt;code&gt;---&lt;/code&gt;), comparing changes with traditional diff tools becomes a mess.&lt;/p&gt;

&lt;p&gt;These tools treat the file as a single blob of text, often producing confusing or outright incorrect diffs. This problem gets worse with large YAML files packed with many documents, making it nearly impossible to spot specific changes.&lt;/p&gt;

&lt;p&gt;That’s why I created &lt;a href="https://github.com/rgeraskin/godiffyaml" rel="noopener noreferrer"&gt;&lt;strong&gt;godiffyaml&lt;/strong&gt;&lt;/a&gt;, a tool designed to solve this exact issue. Whether you’re a developer, DevOps engineer, or system admin, &lt;strong&gt;godiffyaml&lt;/strong&gt; makes diffing multi-document YAML files painless.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Standard Diff Tools Struggle
&lt;/h2&gt;

&lt;p&gt;Here’s what makes diffing multi-document YAML tricky with standard tools:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Document Order Awareness:&lt;/strong&gt; If a document is moved within a YAML file, standard diff tools display it as a completely new document. Any changes inside it might be overlooked.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Document Awareness&lt;/strong&gt;: Tools like &lt;code&gt;diff&lt;/code&gt; don't recognize YAML's &lt;code&gt;---&lt;/code&gt; separators, so changes across documents get mixed up.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Values Notation Awareness&lt;/strong&gt;: According to the YAML specification, the way values are noted is quite flexible. For example, strings can be quoted or not. However, for diff tools, these differences still appear as changes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Nested Structure Awareness&lt;/strong&gt;: YAML’s hierarchical structure—nested keys and values—means small changes can drown in a flood of irrelevant differences.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I built &lt;strong&gt;godiffyaml&lt;/strong&gt; to address these pain points head-on, offering a smarter way to compare multi-document YAML files.&lt;/p&gt;

&lt;p&gt;Let's explain the issues and how &lt;strong&gt;godiffyaml&lt;/strong&gt; solves them in detail.&lt;/p&gt;

&lt;h3&gt;
  
  
  Document Order Awareness
&lt;/h3&gt;

&lt;p&gt;A basic YAML file with random Kubernetes manifests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-config&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;DB_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost"&lt;/span&gt;
  &lt;span class="na"&gt;DB_PORT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5432"&lt;/span&gt;
  &lt;span class="na"&gt;CACHE_TTL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3600"&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;frontend-svc&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3000&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;frontend&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now reverse the order of the documents: place &lt;code&gt;kind: Service&lt;/code&gt; before &lt;code&gt;kind: ConfigMap&lt;/code&gt; and then compare the differences.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fifk91jaftxb78qx2sfsc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fifk91jaftxb78qx2sfsc.png" alt=" " width="800" height="531"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Can you quickly tell if the &lt;code&gt;ConfigMap&lt;/code&gt; has just been moved in the YAML, or if there are additional changes? Imagine having many such "movements" inside; reviewing them would become a nightmare.&lt;/p&gt;

&lt;p&gt;With &lt;strong&gt;godiffyaml&lt;/strong&gt;, you can sort YAML files by path, such as &lt;code&gt;.kind&lt;/code&gt;, and compare them with any standard tool.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1sfg7ycayuzoscbmunz8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1sfg7ycayuzoscbmunz8.png" alt=" " width="800" height="116"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or just&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7h96m74hon1w1qs7fvtn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7h96m74hon1w1qs7fvtn.png" alt=" " width="800" height="206"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Document Awareness
&lt;/h3&gt;

&lt;p&gt;Let's change the value of &lt;code&gt;PGHOST&lt;/code&gt; from &lt;code&gt;host2&lt;/code&gt; to &lt;code&gt;host-changed&lt;/code&gt; in &lt;code&gt;app2-secret&lt;/code&gt; within a YAML file containing two secrets, and then compare the original with the modified version.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Secret&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app1-secret&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;stringData&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;PGDATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db1&lt;/span&gt;
  &lt;span class="na"&gt;PGHOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;host1&lt;/span&gt;
  &lt;span class="na"&gt;PGPASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pass1&lt;/span&gt;
  &lt;span class="na"&gt;PGUSER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;user1&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Secret&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app2-secret&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;stringData&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;PGDATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db2&lt;/span&gt;
  &lt;span class="na"&gt;PGHOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;host2&lt;/span&gt; &lt;span class="c1"&gt;# this will be changed&lt;/span&gt;
  &lt;span class="na"&gt;PGPASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pass2&lt;/span&gt;
  &lt;span class="na"&gt;PGUSER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;user2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl2rfmefw8d1duv7rqk0s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl2rfmefw8d1duv7rqk0s.png" alt=" " width="800" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Can you guess which secret has changed? To find its name, you need to expand the context for the difft tool or check the full diff in your favorite diff tool. Now, imagine there are many such changes across several documents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;godiffyaml&lt;/strong&gt; solves this problem:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftegtki3qf55ofuz84v70.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftegtki3qf55ofuz84v70.png" alt=" " width="800" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See the name in a rectangle? It's templated from the &lt;code&gt;-paths&lt;/code&gt; flag: &lt;code&gt;&amp;lt;.kind&amp;gt;_&amp;lt;.metadata.name&amp;gt;.yaml&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Values Notation Awareness
&lt;/h3&gt;

&lt;p&gt;Let’s compare the same YAML files, but the first one will have unquoted strings and the second one will have quoted strings. According to the YAML specification, they are considered the same, but your IDE probably doesn't see it that way.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl9nd6k57yr2hiajrp5nk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl9nd6k57yr2hiajrp5nk.png" alt=" " width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's just distracting noise because it's YAML with the same values.&lt;/p&gt;

&lt;p&gt;Just like the &lt;em&gt;'No Document Order Awareness'&lt;/em&gt; example above, we can either use &lt;code&gt;godiffyaml sort&lt;/code&gt; and compare with a usual tool or simply use &lt;code&gt;godiffyaml diff&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But now let's use the &lt;code&gt;k8s&lt;/code&gt; subcommand: &lt;code&gt;godiffyaml k8s&lt;/code&gt; is a shortcut for &lt;code&gt;godiffyaml diff -paths apiVersion,kind,metadata.namespace,metadata.name&lt;/code&gt;. Notice that the dot at the beginning of each path is optional.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu1ydrqf0k3ptk3xrsm2m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu1ydrqf0k3ptk3xrsm2m.png" alt=" " width="800" height="239"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that we don’t use the &lt;code&gt;-order&lt;/code&gt; flag for the &lt;code&gt;sort&lt;/code&gt; subcommand. Details are provided below in the &lt;em&gt;‘Magic’&lt;/em&gt; section.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nested Structure Awareness
&lt;/h3&gt;

&lt;p&gt;Typical diff tools show changes for lines, not values. However, it's useful to see specific differences. &lt;a href="https://github.com/Wilfred/difftastic" rel="noopener noreferrer"&gt;Difftastic&lt;/a&gt; addresses this issue, and &lt;strong&gt;godiffyaml&lt;/strong&gt; uses it behind the scenes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Difftastic — a structural diff tool that compares files based on their syntax. It’s a great tool that shows differences only for elements that changed, not the lines.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How GoDiffYAML Works
&lt;/h2&gt;

&lt;p&gt;It has two subcommands: &lt;code&gt;sort&lt;/code&gt; and &lt;code&gt;diff&lt;/code&gt;. Let's break them down first.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sort subcommand
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Reads a YAML file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sorts documents inside by YAML paths &lt;strong&gt;if&lt;/strong&gt; the &lt;code&gt;-order&lt;/code&gt; flag is provided.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Outputs the YAML file with all documents to stdout.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Diff subcommand
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Reads YAML files.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Saves each document in a temporary directory with names based on path values.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;E.g. for&lt;/em&gt; &lt;code&gt;paths=apiVersion,kind,metadata.namespace,metadata.name&lt;/code&gt; &lt;em&gt;document name will be like&lt;/em&gt; &lt;code&gt;apps_v1_Deployment_default_backend-api.yaml&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Runs &lt;strong&gt;difftastic&lt;/strong&gt; on the directories to compare them.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  💫 Magic 💫
&lt;/h3&gt;

&lt;p&gt;All the magic inside is based on two ideas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;By reading and then dumping YAML with the same Go YAML library, we ensure that YAML documents have consistent value notation and key ordering. This applies to both &lt;code&gt;sort&lt;/code&gt; and &lt;code&gt;diff&lt;/code&gt; modes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;By saving files with templated names, we can compare directories with &lt;strong&gt;difftastic&lt;/strong&gt;, which will display the file names. The file names help us identify documents due to the templating.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The structural diff relies entirely on &lt;strong&gt;difftastic&lt;/strong&gt; because I don't want to reinvent the wheel. So, we need &lt;strong&gt;difftastic&lt;/strong&gt; to use the &lt;code&gt;diff&lt;/code&gt; or &lt;code&gt;k8s&lt;/code&gt; subcommands.&lt;/p&gt;

&lt;p&gt;Additionally, any extra flags for &lt;strong&gt;godiffyaml&lt;/strong&gt; are passed to the &lt;strong&gt;difftastic&lt;/strong&gt; backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why You’ll Love GoDiffYAML
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Saves Time&lt;/strong&gt;: No more digging through messy diffs to find what changed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reduces Errors&lt;/strong&gt;: Clear, structured output means fewer mistakes when reviewing updates.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Handles Scale&lt;/strong&gt;: Works seamlessly with large, complex YAML files.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrap-Up
&lt;/h2&gt;

&lt;p&gt;If multi-document YAML files are part of your daily grind, &lt;strong&gt;godiffyaml&lt;/strong&gt; is here to make your life easier. By respecting YAML’s structure and delivering precise, readable diffs, it turns a frustrating task into a breeze. Give it a try — I think you’ll wonder how you ever managed without it!&lt;/p&gt;

&lt;p&gt;So go diff that YAML💪&lt;/p&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>k8s</category>
      <category>yaml</category>
    </item>
    <item>
      <title>AI with Kubernetes: Operations for Developers 🤖</title>
      <dc:creator>Roman Geraskin</dc:creator>
      <pubDate>Sun, 30 Mar 2025 15:42:00 +0000</pubDate>
      <link>https://dev.to/rgeraskin/ai-with-kubernetes-operations-for-developers-i58</link>
      <guid>https://dev.to/rgeraskin/ai-with-kubernetes-operations-for-developers-i58</guid>
      <description>&lt;p&gt;Artificial Intelligence is making Kubernetes cluster management accessible to everyone, even those without deep Kubernetes expertise. By integrating AI tools through the Model Context Protocol (MCP), beginners can interact with clusters using natural language, avoiding complex &lt;code&gt;kubectl&lt;/code&gt; commands.&lt;/p&gt;

&lt;p&gt;This article explores three beginner-friendly approaches — &lt;a href="https://claude.ai/download" rel="noopener noreferrer"&gt;Claude Desktop&lt;/a&gt;, &lt;a href="https://www.cursor.com" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt;, and &lt;a href="https://k9scli.io" rel="noopener noreferrer"&gt;K9S&lt;/a&gt; with &lt;a href="https://github.com/robusta-dev/holmesgpt" rel="noopener noreferrer"&gt;HolmesGPT&lt;/a&gt; — each designed to simplify Kubernetes management for newcomers.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Model Context Protocol (MCP) is a powerful framework for integrating AI models into development environments. MCP enables seamless communication between AI systems and tools like Kubernetes by providing a standardized way to pass context and commands. With MCP, you can query cluster states, automate resource management, or even troubleshoot issues using natural language. To use MCP effectively with Kubernetes, you’ll need a compatible server setup that bridges your AI tool with kubectl, the Kubernetes command-line interface.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are many MCPs for Kubernetes, each with different features and programming languages like TypeScript, Go, and Python. They are still being actively developed, so there are some bugs. They are not quite ready for serious everyday use yet.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://github.com/Flux159/mcp-server-kubernetes" rel="noopener noreferrer"&gt;https://github.com/Flux159/mcp-server-kubernetes&lt;/a&gt; - TypeScript. This MCP is mentioned in the Anthropic documentation and looks promising. It also has more GitHub stars than the others.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rohitg00/kubectl-mcp-server" rel="noopener noreferrer"&gt;https://github.com/rohitg00/kubectl-mcp-server&lt;/a&gt; - A lot of declared functionality but currently less stable than others, Python-based.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/strowk/mcp-k8s-go" rel="noopener noreferrer"&gt;https://github.com/strowk/mcp-k8s-go&lt;/a&gt; - Golang-based&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/manusa/kubernetes-mcp-server" rel="noopener noreferrer"&gt;https://github.com/manusa/kubernetes-mcp-server&lt;/a&gt; - Golang-based, but not just a wrapper around &lt;code&gt;kubectl&lt;/code&gt; or &lt;code&gt;helm&lt;/code&gt;—it doesn't require external dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/wenhuwang/mcp-k8s-eye" rel="noopener noreferrer"&gt;https://github.com/wenhuwang/mcp-k8s-eye&lt;/a&gt; - Golang&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this article, I use the first two to demonstrate basic functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 1: Claude Desktop
&lt;/h2&gt;

&lt;p&gt;In my opinion, &lt;a href="https://claude.ai/download" rel="noopener noreferrer"&gt;Claude Desktop&lt;/a&gt; is an excellent solution for communicating with MCP servers today because of its user-friendly interface. I will use &lt;code&gt;mcp-server-kubernetes&lt;/code&gt; to link Claude with Kubernetes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Configure Claude Desktop
&lt;/h3&gt;

&lt;p&gt;1) Configure Claude Desktop MCP Settings: go to &lt;strong&gt;Settings =&amp;gt; Developer&lt;/strong&gt; menu to add MCP server or just edit &lt;code&gt;~/Library/Application Support/Claude/claude_desktop_config.json&lt;/code&gt; (macOS) with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"kubernetes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"mcp-server-kubernetes"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2) Save and restart Claude.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Chat with Your Cluster
&lt;/h3&gt;

&lt;p&gt;Open a chat in Claude and ask something like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“List deployments in default k8s ns”&lt;/li&gt;
&lt;li&gt;“Show me nginx logs”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbhjph0dwsrpt2ljej2ia.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbhjph0dwsrpt2ljej2ia.png" width="800" height="720"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;See full example conversation &lt;a href="https://claude.ai/share/6c2c4d8d-43ae-422f-bcfa-c12dec42913a" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Claude uses &lt;code&gt;mcp-server-kubernetes&lt;/code&gt; to handle the technical details, making Kubernetes approachable for beginners by translating plain English into actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 2: Cursor IDE
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.cursor.com" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt; is a VS Code fork designed to be a more AI-focused code editor. While it may not be as convenient as Claude, if you spend a lot of time in an IDE, it can be useful to have Kubernetes access with natural language right in the same window.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Install mcp server
&lt;/h3&gt;

&lt;p&gt;Now I will use &lt;a href="https://github.com/rohitg00/kubectl-mcp-server" rel="noopener noreferrer"&gt;kubectl-mcp-server&lt;/a&gt;. You can also use the MCP server from the previous approach or any other from the list of MCPs mentioned earlier—they are interchangeable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pipx &lt;span class="nb"&gt;install &lt;/span&gt;kubectl-mcp-tool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Configure Cursor
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Cursor =&amp;gt; Settings =&amp;gt; Cursor Settings =&amp;gt; MCP&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;"Add new global MCP server"&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Add:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"kubernetes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"~/.local/pipx/venvs/kubectl-mcp-tool/bin/python"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"-m"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"kubectl_mcp_tool.minimal_wrapper"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Interact with Your Cluster
&lt;/h3&gt;

&lt;p&gt;Open chat in Cursor and use prompts like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“List all pods in default k8s ns”&lt;/li&gt;
&lt;li&gt;“What is the status of my nginx k8s deployment?”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjh1drbaizpd3u0r6yax5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjh1drbaizpd3u0r6yax5.png" width="800" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 3: Using K9s with HolmesGPT
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/robusta-dev/holmesgpt" rel="noopener noreferrer"&gt;HolmesGPT&lt;/a&gt;, from Robusta, is a tool that simplifies Kubernetes troubleshooting. It investigates issues automatically, requiring no prior expertise. Use it via the Robusta SaaS platform or CLI with queries like &lt;code&gt;holmes ask "what pods are unhealthy and why?"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We can integrate it with k9s, a terminal-based UI that allows you to interact with your Kubernetes clusters. It's a popular tool for working with Kubernetes, making it easier to navigate, observe, and manage deployed applications.&lt;/p&gt;

&lt;p&gt;Actually, this approach is different from the previous ones. It doesn’t use MCP. However, it is powerful, so I have to mention it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Configuring K9s with HolmesGPT
&lt;/h3&gt;

&lt;p&gt;1) Install HolmesGPT (&lt;a href="https://github.com/robusta-dev/holmesgpt/blob/master/docs/installation.md#cli-installation" rel="noopener noreferrer"&gt;instructions&lt;/a&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# for mac&lt;/span&gt;
brew tap robusta-dev/homebrew-holmesgpt
brew &lt;span class="nb"&gt;install &lt;/span&gt;holmesgpt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2) Export OpenAI API key, for example with &lt;code&gt;~/.zshrc&lt;/code&gt;: &lt;code&gt;export OPENAI_API_KEY=XXX&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I bet you can easily Google how to get an API key. Certainly, you can use &lt;a href="https://github.com/robusta-dev/holmesgpt/blob/master/docs/api-keys.md" rel="noopener noreferrer"&gt;other AIs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;3) Add Holmes as plugin to k9s: edit &lt;code&gt;~/Library/Application Support/k9s/plugins.yaml&lt;/code&gt; (mac). See detailed instructions &lt;a href="https://github.com/robusta-dev/holmesgpt/blob/master/docs/k9s.md" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;   &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;holmesgpt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="na"&gt;shortCut&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Shift-H&lt;/span&gt;
       &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ask HolmesGPT&lt;/span&gt;
       &lt;span class="na"&gt;scopes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;all&lt;/span&gt;
       &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
       &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
       &lt;span class="na"&gt;confirm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
       &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-c&lt;/span&gt;
         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
           &lt;span class="s"&gt;holmes ask "why is $NAME of $RESOURCE_NAME in namespace $NAMESPACE not working as expected?"&lt;/span&gt;
           &lt;span class="s"&gt;echo "Press 'q' to exit"&lt;/span&gt;
           &lt;span class="s"&gt;while : ; do&lt;/span&gt;
           &lt;span class="s"&gt;read -n 1 k &amp;lt;&amp;amp;1&lt;/span&gt;
           &lt;span class="s"&gt;if [[ $k = q ]] ; then&lt;/span&gt;
           &lt;span class="s"&gt;break&lt;/span&gt;
           &lt;span class="s"&gt;fi&lt;/span&gt;
           &lt;span class="s"&gt;done&lt;/span&gt;
     &lt;span class="na"&gt;custom-holmesgpt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="na"&gt;shortCut&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Shift-Q&lt;/span&gt;
       &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Custom HolmesGPT Ask&lt;/span&gt;
       &lt;span class="na"&gt;scopes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;all&lt;/span&gt;
       &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
       &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
       &lt;span class="na"&gt;confirm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
       &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-c&lt;/span&gt;
         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
           &lt;span class="s"&gt;INSTRUCTIONS="# Edit the line below. Lines starting with '#' will be ignored."&lt;/span&gt;
           &lt;span class="s"&gt;DEFAULT_ASK_COMMAND="why is $NAME of $RESOURCE_NAME in namespace $NAMESPACE not working as expected?"&lt;/span&gt;
           &lt;span class="s"&gt;QUESTION_FILE=$(mktemp)&lt;/span&gt;

           &lt;span class="s"&gt;echo "$INSTRUCTIONS" &amp;gt; "$QUESTION_FILE"&lt;/span&gt;
           &lt;span class="s"&gt;echo "$DEFAULT_ASK_COMMAND" &amp;gt;&amp;gt; "$QUESTION_FILE"&lt;/span&gt;

           &lt;span class="s"&gt;# Open the line in the default text editor&lt;/span&gt;
           &lt;span class="s"&gt;${EDITOR:-nano} "$QUESTION_FILE"&lt;/span&gt;

           &lt;span class="s"&gt;# Read the modified line, ignoring lines starting with '#'&lt;/span&gt;
           &lt;span class="s"&gt;user_input=$(grep -v '^#' "$QUESTION_FILE")&lt;/span&gt;
           &lt;span class="s"&gt;echo running: holmes ask "\"$user_input\""&lt;/span&gt;

           &lt;span class="s"&gt;holmes ask "$user_input"&lt;/span&gt;
           &lt;span class="s"&gt;echo "Press 'q' to exit"&lt;/span&gt;
           &lt;span class="s"&gt;while : ; do&lt;/span&gt;
           &lt;span class="s"&gt;read -n 1 k &amp;lt;&amp;amp;1&lt;/span&gt;
           &lt;span class="s"&gt;if [[ $k = q ]] ; then&lt;/span&gt;
           &lt;span class="s"&gt;break&lt;/span&gt;
           &lt;span class="s"&gt;fi&lt;/span&gt;
           &lt;span class="s"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3) Start K9s, select pod and ask Holmes with &lt;code&gt;Shift-H&lt;/code&gt; or &lt;code&gt;Shift-Q&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1743201575892%2F8c713aca-2265-47c9-9ddd-55e478e9513b.png%2520align%3D" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1743201575892%2F8c713aca-2265-47c9-9ddd-55e478e9513b.png%2520align%3D" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;HolmesGPT can do much more. Check the &lt;a href="https://github.com/robusta-dev/holmesgpt" rel="noopener noreferrer"&gt;repository&lt;/a&gt; for more details. Also, there are other tools, like &lt;a href="https://k8sgpt.ai" rel="noopener noreferrer"&gt;k8sgpt&lt;/a&gt;, that can make the life easier. Maybe some of them can be integrated with Lens too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters for Beginners
&lt;/h2&gt;

&lt;p&gt;These tools democratize Kubernetes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude&lt;/strong&gt;: A desktop app where anyone can chat with their cluster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cursor&lt;/strong&gt;: An IDE that lets developers code and manage k8s without expertise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;K9s&lt;/strong&gt;: A visual tool with AI to troubleshoot effortlessly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Needless to say, all of this is not meant for production use. Moreover, MCP servers are still full of bugs and have limited functionality. However, it can still be useful for local development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Claude Desktop, Cursor, and K9s with HolmesGPT empower beginners to manage Kubernetes clusters with AI, no solid K8s experience required. They turn tasks into simple conversations, making DevOps accessible to all. Try them out and start exploring Kubernetes!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>ai</category>
      <category>kubernetes</category>
      <category>chatgpt</category>
    </item>
    <item>
      <title>Automating Development Environment with Mise: Comprehensive Guide 💫</title>
      <dc:creator>Roman Geraskin</dc:creator>
      <pubDate>Wed, 12 Mar 2025 21:12:00 +0000</pubDate>
      <link>https://dev.to/rgeraskin/automating-development-environment-with-mise-comprehensive-guide-jbh</link>
      <guid>https://dev.to/rgeraskin/automating-development-environment-with-mise-comprehensive-guide-jbh</guid>
      <description>&lt;p&gt;When working in a team, it's important for everyone to have a consistent environment. Also, different projects might require different versions of tools.&lt;/p&gt;

&lt;p&gt;Additionally, automating routine tasks with the codebase is helpful, so some form of task management is also necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;As always, there are many tools available to solve these problems. For example, to create a development environment, there are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Language-specific tools&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Node.js: nvm / n&lt;/li&gt;
&lt;li&gt;Python: venv / pyenv / poetry / conda&lt;/li&gt;
&lt;li&gt;Ruby: rbenv&lt;/li&gt;
&lt;li&gt;Java: jenv&lt;/li&gt;
&lt;li&gt;Infrastructure tools:

&lt;ol&gt;
&lt;li&gt;tfenv for Terraform&lt;/li&gt;
&lt;li&gt;kbenv for kubectl&lt;/li&gt;
&lt;li&gt;helmenv for Helm&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;Environment variables: &lt;a href="https://github.com/direnv/direnv" rel="noopener noreferrer"&gt;direnv&lt;/a&gt;
&lt;/li&gt;

&lt;/ol&gt;

&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Language-agnostic tools&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://asdf-vm.com" rel="noopener noreferrer"&gt;asdf&lt;/a&gt; - popular all-in-one solution&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nix.dev" rel="noopener noreferrer"&gt;nix&lt;/a&gt; - it’s better to have a lot of free time, lol&lt;/li&gt;
&lt;li&gt;or even &lt;a href="https://code.visualstudio.com/docs/devcontainers/tutorial" rel="noopener noreferrer"&gt;devcontainers&lt;/a&gt; for VS Code: the docker-way&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;p&gt;As we can see, there are many tools available 🤯, and there are even more for task management. While we can use &lt;code&gt;make&lt;/code&gt;, it's not very user-friendly. So, there are many alternatives to &lt;code&gt;make&lt;/code&gt;. Trust me, I've tried most of them&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://taskfile.dev" rel="noopener noreferrer"&gt;task&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/casey/just" rel="noopener noreferrer"&gt;just&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/earthly/earthly" rel="noopener noreferrer"&gt;Earthly&lt;/a&gt; - docker inside 🤟&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/trinio-labs/bake" rel="noopener noreferrer"&gt;bake&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/TekWizely/run" rel="noopener noreferrer"&gt;run&lt;/a&gt; - last commit year ago&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/xonixx/makesure" rel="noopener noreferrer"&gt;&lt;strong&gt;makesure&lt;/strong&gt;&lt;/a&gt; - weird syntax&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/zaaack/foy" rel="noopener noreferrer"&gt;foy&lt;/a&gt; - if we love js&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/tj/mmake" rel="noopener noreferrer"&gt;mmake&lt;/a&gt; - &lt;code&gt;make&lt;/code&gt; on steroids&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/tj/robo" rel="noopener noreferrer"&gt;robo&lt;/a&gt; - last commit 2 years ago&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/apenwarr/redo" rel="noopener noreferrer"&gt;redo&lt;/a&gt; - last commit 3 years ago&lt;/li&gt;
&lt;li&gt;Or even &lt;a href="https://buck2.build" rel="noopener noreferrer"&gt;buck2&lt;/a&gt; / &lt;a href="https://github.com/bazelbuild/bazel" rel="noopener noreferrer"&gt;bazel&lt;/a&gt; / &lt;a href="https://github.com/pantsbuild/pants" rel="noopener noreferrer"&gt;pants&lt;/a&gt; / &lt;a href="https://please.build" rel="noopener noreferrer"&gt;please&lt;/a&gt; if we already have it in the project&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I actually really like the first three.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing Mise: Development Tools and Tasks in One App
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://mise.jdx.dev" rel="noopener noreferrer"&gt;Mise&lt;/a&gt;, inspired by &lt;a href="https://asdf-vm.com" rel="noopener noreferrer"&gt;asdf&lt;/a&gt; — the multiple runtime version manager, can use asdf’s repository with hundreds of tools as its successor. However, mise goes further:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It supports several other "backends" to install tools outside the built-in repository.&lt;/li&gt;
&lt;li&gt;It has a simpler CLI.&lt;/li&gt;
&lt;li&gt;Tasks!&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Define a Toolset
&lt;/h3&gt;

&lt;p&gt;Mise is set up using a single file called &lt;code&gt;.mise.toml&lt;/code&gt;. Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# .mise.toml
&lt;/span&gt;&lt;span class="nn"&gt;[tools]&lt;/span&gt;
&lt;span class="py"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.9"&lt;/span&gt;
&lt;span class="py"&gt;terramate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.9"&lt;/span&gt;
&lt;span class="py"&gt;pre-commit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"3"&lt;/span&gt;
&lt;span class="py"&gt;awscli&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2"&lt;/span&gt;
&lt;span class="err"&gt;"pipx:detect-secrets"&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="err"&gt;"1.4"&lt;/span&gt;
&lt;span class="err"&gt;"go:github.com/containerscrew/tftools"&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="err"&gt;"0.9.0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I can install it with a single command: &lt;code&gt;mise install&lt;/code&gt;, see a demo gif in the next sections. Let me explain the contents.&lt;/p&gt;

&lt;p&gt;Our team uses this file for the infrastructure repository with Terraform and &lt;a href="https://github.com/terramate-io/terramate" rel="noopener noreferrer"&gt;Terramate&lt;/a&gt; manifests. Terraform requires awscli to work with the AWS API provider.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;detect-secrets&lt;/code&gt; is a tool used in a &lt;a href="https://github.com/pre-commit/pre-commit" rel="noopener noreferrer"&gt;pre-commit hook&lt;/a&gt; to ensure no secrets are accidentally exposed to git. This tool is installed with &lt;code&gt;pipx&lt;/code&gt;, and mise supports this backend out of the box.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tftools&lt;/code&gt; is a tool that summarizes changes in Terraform plans. It's useful if you want to review plans for several environments or stacks at once. As we can see, it uses the Go backend to fetch a binary from the repository.&lt;/p&gt;

&lt;p&gt;There are many other backends: &lt;a href="https://mise.jdx.dev/dev-tools/backends/asdf.html" rel="noopener noreferrer"&gt;asdf&lt;/a&gt;, &lt;a href="https://mise.jdx.dev/dev-tools/backends/cargo.html" rel="noopener noreferrer"&gt;cargo&lt;/a&gt;, &lt;a href="https://mise.jdx.dev/dev-tools/backends/go.html" rel="noopener noreferrer"&gt;go&lt;/a&gt;, &lt;a href="https://mise.jdx.dev/dev-tools/backends/npm.html" rel="noopener noreferrer"&gt;npm&lt;/a&gt;, &lt;a href="https://mise.jdx.dev/dev-tools/backends/pipx.html" rel="noopener noreferrer"&gt;pipx&lt;/a&gt;, &lt;a href="https://mise.jdx.dev/dev-tools/backends/spm.html" rel="noopener noreferrer"&gt;spm&lt;/a&gt;, &lt;a href="https://mise.jdx.dev/dev-tools/backends/ubi.html" rel="noopener noreferrer"&gt;ubi&lt;/a&gt;, &lt;a href="https://mise.jdx.dev/dev-tools/backends/vfox.html" rel="noopener noreferrer"&gt;vfox&lt;/a&gt;. Honestly, I'm not sure what spm and vfox are 🙂, but ubi is &lt;a href="https://github.com/houseabsolute/ubi" rel="noopener noreferrer"&gt;The Universal Binary Installer&lt;/a&gt;, which lets you install any binary from a tool’s GitHub release page.&lt;/p&gt;

&lt;p&gt;Need to add more tools? Edit the file or run &lt;code&gt;mise use TOOL_NAME&lt;/code&gt;. To see the built-in tool registry, run &lt;code&gt;mise registry&lt;/code&gt;. For tools not listed there, we can use the full notation, as I did with &lt;em&gt;tftools&lt;/em&gt; above.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Manage Environments&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;By default, mise installs a tool &lt;strong&gt;not system-wide&lt;/strong&gt;. This means we can have different tool versions for different directories aka projects. If you prefer a system-wide installation, you can configure it by placing the settings in &lt;code&gt;~/.config/mise/config.toml&lt;/code&gt;, making tools and tasks (see below) available in any directory.&lt;/p&gt;

&lt;p&gt;Mise supports nested configurations. To find out which configuration provides a tool or task, run &lt;code&gt;mise ls&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, you can have different Python versions for different projects. Mise determines which Python version to use based on the nearest &lt;code&gt;.mise.toml&lt;/code&gt; file. It even supports &lt;a href="https://mise.jdx.dev/lang/python.html#automatic-virtualenv-activation" rel="noopener noreferrer"&gt;automatic virtualenv activation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can also set different environment variables for different directories, similar to &lt;a href="https://github.com/direnv/direnv" rel="noopener noreferrer"&gt;direnv&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prepare Dev Env
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;brew install mise&lt;/code&gt; or &lt;a href="https://mise.jdx.dev/getting-started.html#alternate-installation-methods" rel="noopener noreferrer"&gt;use alternative installation methods&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://mise.jdx.dev/getting-started.html#_2a-activate-mise" rel="noopener noreferrer"&gt;Activate mise&lt;/a&gt; in your shell. Zsh example:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'eval "$(~/.local/bin/mise activate zsh)"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.zshrc
&lt;span class="c"&gt;# restart shell or `source ~/.zshrc`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;mise install&lt;/code&gt; in a dir with &lt;code&gt;.mise.toml&lt;/code&gt; file or use my &lt;a href="https://marketplace.visualstudio.com/items?itemName=rgeraskin.mise" rel="noopener noreferrer"&gt;VSCode extension&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, add this simple instruction to the repo's &lt;code&gt;README.md&lt;/code&gt;, place &lt;code&gt;.mise.toml&lt;/code&gt; in the root of the repo, and your teammates can simply run &lt;code&gt;mise install&lt;/code&gt; to get the same toolset. Make sure to pin tool versions to ensure consistency. 🙂&lt;/p&gt;

&lt;p&gt;Also, mise is cross-platform, so people using Linux or Mac will follow the same instructions. No more juggling with brew, apt, or yum.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run Tasks
&lt;/h3&gt;

&lt;p&gt;Let's define several tasks to make daily routines easier with that infrastructure repo. To run Terraform, &lt;code&gt;*.tf&lt;/code&gt; files should be generated with Terramate. To create a plan, the &lt;em&gt;tf-state&lt;/em&gt; must be initialized. To initialize a state, it's best to be logged into AWS. That's a lot to keep track of.&lt;/p&gt;

&lt;p&gt;We can use mise tasks to make the process easier. I've removed the Terramate and SSO details to keep the example brief:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# .mise.toml
&lt;/span&gt;
&lt;span class="nn"&gt;[tasks."tf.init"]&lt;/span&gt;
&lt;span class="py"&gt;alias&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"tfi"&lt;/span&gt;
&lt;span class="py"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"`terraform init`"&lt;/span&gt;
&lt;span class="py"&gt;run&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"terraform init"&lt;/span&gt;

&lt;span class="nn"&gt;[tasks."tf.validate"]&lt;/span&gt;
&lt;span class="py"&gt;alias&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"tfv"&lt;/span&gt;
&lt;span class="py"&gt;depends&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;["tf.init"]&lt;/span&gt;
&lt;span class="py"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"`terraform validate`"&lt;/span&gt;
&lt;span class="py"&gt;run&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"terraform validate"&lt;/span&gt;

&lt;span class="nn"&gt;[tasks."tf.plan"]&lt;/span&gt;
&lt;span class="py"&gt;alias&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"tfp"&lt;/span&gt;
&lt;span class="py"&gt;depends&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;["tf.init"]&lt;/span&gt;
&lt;span class="py"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"`terraform plan`"&lt;/span&gt;
&lt;span class="py"&gt;run&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
#!/usr/bin/env bash

# can use args for this task
terraform plan -out=tfplan $@ 
# ring terminal when complete
tput bel 
"""&lt;/span&gt;

&lt;span class="nn"&gt;[tasks."tf.summarize"]&lt;/span&gt;
&lt;span class="py"&gt;alias&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"tfs"&lt;/span&gt;
&lt;span class="py"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"`tftools summarize` with pre-generated plan file (terraform plan should be generated in advance)"&lt;/span&gt;
&lt;span class="py"&gt;run&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
#!/usr/bin/env bash

terraform show -json tfplan &amp;gt; plan.json
tftools summarize &amp;lt; plan.json
"""&lt;/span&gt;

&lt;span class="nn"&gt;[tasks."tf.apply"]&lt;/span&gt;
&lt;span class="py"&gt;alias&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"tfa"&lt;/span&gt;
&lt;span class="py"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"`terraform apply` with pre-generated plan file (terraform plan should be generated in advance)"&lt;/span&gt;
&lt;span class="py"&gt;run&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
#!/usr/bin/env bash

cmd="terraform apply tfplan $@"&lt;/span&gt;
&lt;span class="err"&gt;read&lt;/span&gt; &lt;span class="err"&gt;-p&lt;/span&gt; &lt;span class="err"&gt;"$cmd\n\nAre&lt;/span&gt; &lt;span class="err"&gt;you&lt;/span&gt; &lt;span class="err"&gt;sure?&lt;/span&gt; &lt;span class="err"&gt;(Y/n)&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt; &lt;span class="err"&gt;choice&lt;/span&gt;
&lt;span class="nn"&gt;[ "$choice" = "n" ]&lt;/span&gt; &lt;span class="err"&gt;||&lt;/span&gt; &lt;span class="err"&gt;($cmd&lt;/span&gt;&lt;span class="c"&gt;; tput bel)
&lt;/span&gt;&lt;span class="err"&gt;"""&lt;/span&gt;

&lt;span class="nn"&gt;[tasks."tf.lock"]&lt;/span&gt;
&lt;span class="py"&gt;depends&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;["tf.init"]&lt;/span&gt;
&lt;span class="py"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"`terraform providers lock` with predefined platform list"&lt;/span&gt;
&lt;span class="py"&gt;run&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"terraform providers lock -platform=linux_amd64 -platform=darwin_amd64 -platform=darwin_arm64 ; tput bel"&lt;/span&gt;

&lt;span class="nn"&gt;[tasks."tf.console"]&lt;/span&gt;
&lt;span class="py"&gt;alias&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"tfc"&lt;/span&gt;
&lt;span class="py"&gt;depends&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;["tf.init"]&lt;/span&gt;
&lt;span class="py"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"`terraform console`"&lt;/span&gt;
&lt;span class="py"&gt;raw&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;run&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"terraform console"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run &lt;em&gt;plan&lt;/em&gt;, use &lt;code&gt;mise run tf.plan&lt;/code&gt;. Mise has aliases, so we can use &lt;code&gt;mise run tfp&lt;/code&gt;. The shell also supports aliases 🙂. So why not create an alias with &lt;code&gt;alias mr="mise run"&lt;/code&gt; and just use &lt;code&gt;mr tfp&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you use VSCode, check out my &lt;a href="https://marketplace.visualstudio.com/items?itemName=rgeraskin.mise" rel="noopener noreferrer"&gt;VSCode extension&lt;/a&gt; to run tasks for your workspace directly from the IDE.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a simple, straightforward flow for Terraform. To see the full example I use daily, you can check &lt;a href="https://gist.github.com/rgeraskin/88c895e393aa8727464401980482f4e0" rel="noopener noreferrer"&gt;this gist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Clean and beautiful syntax, in my opinion.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Makefile is for C developers, while TOML is for humans 😊&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;List all available tasks, and you'll get a nice table with descriptions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;mise tasks

Name          Description                               Source
tf.apply      &lt;span class="sb"&gt;`&lt;/span&gt;terraform apply&lt;span class="sb"&gt;`&lt;/span&gt; with pre-generated pl…  ~/.config/mise/config.toml
tf.console    &lt;span class="sb"&gt;`&lt;/span&gt;terraform console&lt;span class="sb"&gt;`&lt;/span&gt;                       ~/.config/mise/config.toml
tf.init       &lt;span class="sb"&gt;`&lt;/span&gt;terraform init&lt;span class="sb"&gt;`&lt;/span&gt;                          ~/.config/mise/config.toml
tf.lock       &lt;span class="sb"&gt;`&lt;/span&gt;terraform providers lock&lt;span class="sb"&gt;`&lt;/span&gt; with predefi…  ~/.config/mise/config.toml
tf.plan       &lt;span class="sb"&gt;`&lt;/span&gt;terraform plan&lt;span class="sb"&gt;`&lt;/span&gt;                          ~/.config/mise/config.toml
tf.summarize  &lt;span class="sb"&gt;`&lt;/span&gt;tftools summarize&lt;span class="sb"&gt;`&lt;/span&gt; with pre-generated …  ~/.config/mise/config.toml
tf.validate   &lt;span class="sb"&gt;`&lt;/span&gt;terraform validate&lt;span class="sb"&gt;`&lt;/span&gt;                      ~/.config/mise/config.toml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  CI/CD Pipelines
&lt;/h3&gt;

&lt;p&gt;Tired of writing boilerplate code to install necessary tools for pipelines? With &lt;code&gt;.mise.toml&lt;/code&gt;, why not use &lt;code&gt;mise install&lt;/code&gt; in pipelines too? Install mise in advance or use the Docker image &lt;code&gt;jdxcode/mise&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There's no need to track tool versions separately for development environments and CI anymore. You no longer need to remember to update tool versions in different places to prevent any issues. Mise serves as a single source of truth.&lt;/p&gt;

&lt;p&gt;If you use a similar task flow in CI as you do locally, you can run the same mise tasks there too. If not, you can use &lt;a href="https://mise.jdx.dev/profiles.html" rel="noopener noreferrer"&gt;mise profiles&lt;/a&gt; to define CI tasks. Bonus: you can easily use it locally to debug pipeline issues.&lt;/p&gt;

&lt;p&gt;See the &lt;a href="https://mise.jdx.dev/tips-and-tricks.html#ci-cd" rel="noopener noreferrer"&gt;docs&lt;/a&gt; for a GitHub Actions example.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best Practices
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Place &lt;code&gt;.mise.toml&lt;/code&gt; in the root of your Git repository to define tools and tasks for the entire repository.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If a repository contains several different projects (like a monorepo), keep common items (like pre-commit tools) in the root config and project-specific items in the project directories.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use your own &lt;code&gt;~/.config/mise/config.toml&lt;/code&gt; for personal automation tasks.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Mise is a unified tool and task management solution that simplifies the process of installing and managing tools for different projects. It supports various backends for tool installation and allows users to define tasks with custom scripts.&lt;/p&gt;

&lt;p&gt;Mise can be used in both local development environments and CI/CD pipelines, serving as a single source of truth for tool versions and task flows.&lt;/p&gt;

&lt;p&gt;Mise is an excellent alternative to &lt;code&gt;make&lt;/code&gt;, &lt;code&gt;tfenv&lt;/code&gt;, &lt;code&gt;direnv&lt;/code&gt;, and whatever-else-env. &lt;a href="https://mise.jdx.dev" rel="noopener noreferrer"&gt;Try it!&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>makefile</category>
      <category>cicd</category>
      <category>developmentenvironments</category>
    </item>
    <item>
      <title>Mastering Terraform Debugging: Tips and Techniques 🔧</title>
      <dc:creator>Roman Geraskin</dc:creator>
      <pubDate>Sun, 02 Jun 2024 13:21:41 +0000</pubDate>
      <link>https://dev.to/rgeraskin/mastering-terraform-debugging-tips-and-techniques-3jcj</link>
      <guid>https://dev.to/rgeraskin/mastering-terraform-debugging-tips-and-techniques-3jcj</guid>
      <description>&lt;p&gt;There is a wealth of information available about Terraform and the various tools within the Terraform ecosystem. However, there seems to be a noticeable gap when it comes to resources on debugging techniques specific to Terraform.&lt;/p&gt;

&lt;p&gt;Let's address this issue and expand the knowledge base to include detailed debugging methods.&lt;/p&gt;

&lt;h2&gt;
  
  
  Terraform Console
&lt;/h2&gt;

&lt;p&gt;The built-in Terraform feature can make your life much simpler. It's often been avoided by engineers and is not so popular in blogs.&lt;/p&gt;

&lt;p&gt;What can you do with the console? Basically, it can be used not only to show info about a resource in a state.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Drop state info
&lt;/h3&gt;

&lt;p&gt;When you run &lt;code&gt;terraform console&lt;/code&gt; in a Terraform project directory, it tries to use the state information to get actual details. It's useful for getting information about created resources or data objects.&lt;/p&gt;

&lt;p&gt;But it sometimes slows down the debugging process. For example, if you place your state in an S3 bucket, it fetches it first. Tools like &lt;code&gt;terraform-repl&lt;/code&gt; run &lt;code&gt;terraform console&lt;/code&gt; under the hood for every command, making the process really slow. Also, &lt;code&gt;console&lt;/code&gt; sets a lock on the state while you work with it.&lt;/p&gt;

&lt;p&gt;To work with &lt;code&gt;console&lt;/code&gt; without an S3 state, you can comment out the Terraform &lt;code&gt;backend&lt;/code&gt; section, remove the &lt;code&gt;.terraform&lt;/code&gt; folder, and rerun &lt;code&gt;terraform init&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Test your expressions
&lt;/h3&gt;

&lt;p&gt;Often we have to convert data between various formats to meet the requirements of module interfaces or for other cases. And we usually do it blindly, evaluating complicated expressions in our minds only. That leads to unexpected issues in some corner cases.&lt;/p&gt;

&lt;p&gt;Just test it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; coalesce(["", "b"]...)
b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, it's a simple example from the tf docs, but you can use &lt;code&gt;console&lt;/code&gt; for much more complicated things.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Did you know that terraform console can be launched not only in a directory with a terraform project? To test expressions, you can start it in any directory.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3. Define your own &lt;code&gt;locals&lt;/code&gt; interactively
&lt;/h3&gt;

&lt;p&gt;One of the common drawbacks of terraform console is that it doesn't allow you to set your own variables or locals. To compose a complicated expression, it's handy to use intermediate local variables.&lt;/p&gt;

&lt;p&gt;To make it possible, you can use terraform console wrappers such as &lt;a href="https://github.com/paololazzari/terraform-repl" rel="noopener noreferrer"&gt;terraform-repl&lt;/a&gt;. It also adds a tab-completion feature.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; local.a=1
&amp;gt; local.a+1
2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By using the &lt;code&gt;local&lt;/code&gt; command, you can display all local variables.&lt;/p&gt;

&lt;p&gt;There is another tool called &lt;a href="https://github.com/ysoftwareab/tfrepl" rel="noopener noreferrer"&gt;tfrepl&lt;/a&gt; with similar functionality. However, it doesn't have tab-completion, and it can't show all of your defined &lt;code&gt;locals&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Debug your modules
&lt;/h3&gt;

&lt;p&gt;Modules are the way to keep your tf code &lt;a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself" rel="noopener noreferrer"&gt;DRY&lt;/a&gt;. But it's a pain in the neck when you debug and refactor it. Use console tools to make it simpler.&lt;/p&gt;

&lt;p&gt;If you have default values for your variables defined, just run &lt;code&gt;terraform console&lt;/code&gt; or &lt;code&gt;terraform-repl&lt;/code&gt; to debug expressions.&lt;/p&gt;

&lt;p&gt;If you have no default values, it's safe to place &lt;code&gt;terraform.tfvars&lt;/code&gt; just inside the module folder. Terraform will ignore these vars when this module is used somewhere in a parent project.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Test your modules
&lt;/h3&gt;

&lt;p&gt;The practice of testing Terraform code is not widely used in the infrastructure world. Actually, I've never seen tests in any single company :)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Personally, I don't see any significant benefit from tests in the way they are meant to be used. See &lt;a href="https://developer.hashicorp.com/terraform/language/tests#example" rel="noopener noreferrer"&gt;this&lt;/a&gt; example from the official docs: the test checks if the bucket's name is created as expected. &lt;/p&gt;

&lt;p&gt;Do we really need it? We just use the variable in the bucket name, so it's obvious that nothing will happen to it later!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But we can use tests to make sure that we will not break a module's 'interface' sometime in the future while refactoring. Just write a test and do anything with local expressions and variables.&lt;/p&gt;

&lt;p&gt;Stupid simple test example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# main.tftest.hcl&lt;/span&gt;
run &lt;span class="s2"&gt;"test"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; plan
  assert &lt;span class="o"&gt;{&lt;/span&gt;
    condition &lt;span class="o"&gt;=&lt;/span&gt; jsonencode&lt;span class="o"&gt;(&lt;/span&gt;local.users&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; jsonencode&lt;span class="o"&gt;({&lt;/span&gt;
      &lt;span class="s2"&gt;"john"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"grants"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; null
        &lt;span class="s2"&gt;"login"&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
        &lt;span class="s2"&gt;"member_of"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;
          &lt;span class="s2"&gt;"admin"&lt;/span&gt;,
        &lt;span class="o"&gt;]&lt;/span&gt;
        &lt;span class="s2"&gt;"password_enabled"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;})&lt;/span&gt;
    error_message &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Wrong format of local.users : &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;(local.users)&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ terraform &lt;span class="nb"&gt;test
&lt;/span&gt;main.tftest.hcl... &lt;span class="k"&gt;in &lt;/span&gt;progress
  run &lt;span class="s2"&gt;"test"&lt;/span&gt;... pass
main.tftest.hcl... tearing down
main.tftest.hcl... pass

Success! 1 passed, 0 failed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, if you already have default values in your &lt;code&gt;variables.tf&lt;/code&gt;, you are ready to use tests. If you don't, place &lt;code&gt;terraform.tfvars&lt;/code&gt; as stated above.&lt;/p&gt;

&lt;p&gt;I don't recommend placing variables inside &lt;code&gt;*.tftest.hcl&lt;/code&gt; in simple cases because you will not be able to use those variables with &lt;code&gt;console&lt;/code&gt; later. Also, avoid complicated tests. Simple tests are easier to support. So, there is a chance that they will be supported in the future at least.&lt;/p&gt;

&lt;p&gt;Testing expressions is the way to be confident that you will have the expected value in your resource property.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I shared a few tricks on debugging Terraform expressions. Do you have any to add? Share them in the comments.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Terramate meets Atlantis 🚀</title>
      <dc:creator>Roman Geraskin</dc:creator>
      <pubDate>Wed, 03 Apr 2024 21:12:00 +0000</pubDate>
      <link>https://dev.to/rgeraskin/terramate-meets-atlantis-2ce8</link>
      <guid>https://dev.to/rgeraskin/terramate-meets-atlantis-2ce8</guid>
      <description>&lt;p&gt;&lt;a href="https://www.runatlantis.io" rel="noopener noreferrer"&gt;Atlantis&lt;/a&gt; is a pull request automation tool that works well with plain Terraform right away. But what if we're already using &lt;a href="https://github.com/terramate-io/terramate" rel="noopener noreferrer"&gt;Terramate&lt;/a&gt; to generate Terraform code?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Below, I assume the use of the official Atlantis Helm chart for deployment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;1. Add Terramate binary&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add the following to the Atlantis chart's &lt;code&gt;values.yaml&lt;/code&gt; to download the binary and mount it to the Atlantis pod. This allows Atlantis to use it for generating Terraform code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;initContainers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;-&lt;/span&gt;
        &lt;span class="s"&gt;curl -L https://github.com/terramate-io/terramate/releases/download/v${TERRAMATE_VERSION}/terramate_${TERRAMATE_VERSION}_linux_x86_64.tar.gz | tar xz&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sh&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-c&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TERRAMATE_VERSION&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.4.5&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;curlimages/curl&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;get-terramate&lt;/span&gt;
    &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/curl_user/&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terramate&lt;/span&gt;
    &lt;span class="na"&gt;workingDir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/curl_user/&lt;/span&gt;
  &lt;span class="na"&gt;extraVolumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terramate&lt;/span&gt;
      &lt;span class="na"&gt;emptyDir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
  &lt;span class="na"&gt;extraVolumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terramate&lt;/span&gt;
      &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/usr/local/bin/terramate&lt;/span&gt;
      &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terramate&lt;/span&gt;
      &lt;span class="na"&gt;readOnly&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Use server-side config&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's use server-side configuration for our repository. Therefore, add more values to the &lt;code&gt;values.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ATLANTIS_REPO_CONFIG&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/atlantis/server-side-config.yaml&lt;/span&gt;
&lt;span class="na"&gt;extraVolumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;atlantis-server-side-config&lt;/span&gt;
    &lt;span class="na"&gt;configMap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;atlantis-server-side-config&lt;/span&gt;
&lt;span class="na"&gt;extraVolumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;atlantis-server-side-config&lt;/span&gt;
    &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/atlantis/server-side-config.yaml&lt;/span&gt;
    &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;server-side-config.yaml&lt;/span&gt;
    &lt;span class="na"&gt;readOnly&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Deploy server-side config&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now, place the server-side configuration in a configMap and deploy it to the Atlantis namespace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;atlantis-server-side-config&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# I deploy it with the Atlantis chart, so here is helm templating&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Chart.Name&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
    &lt;span class="na"&gt;chart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Chart.Name&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;-{{ .Chart.AppVersion }}&lt;/span&gt;
    &lt;span class="na"&gt;release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Release.Name&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
    &lt;span class="na"&gt;heritage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Release.Service&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;server-side-config.yaml&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;repos:&lt;/span&gt;
    &lt;span class="s"&gt;# this steps run before every workflow. &lt;/span&gt;
    &lt;span class="s"&gt;# So we will generate tf-code here&lt;/span&gt;
    &lt;span class="s"&gt;- pre_workflow_hooks:&lt;/span&gt;
        &lt;span class="s"&gt;# to run 'safeguards' terramate wants origin master &lt;/span&gt;
        &lt;span class="s"&gt;#   and some previous commits&lt;/span&gt;
        &lt;span class="s"&gt;- run: |&lt;/span&gt;
            &lt;span class="s"&gt;git config --add remote.origin.fetch +refs/heads/master:refs/remotes/origin/master&lt;/span&gt;
            &lt;span class="s"&gt;git fetch --depth=1 origin master&lt;/span&gt;
            &lt;span class="s"&gt;git fetch --depth=2&lt;/span&gt;
          &lt;span class="s"&gt;description: Fetch origin master branch&lt;/span&gt;

        &lt;span class="s"&gt;# this step is optional&lt;/span&gt;
        &lt;span class="s"&gt;# I use .tm-run-order later to made Atlantis &lt;/span&gt;
        &lt;span class="s"&gt;#   to run plan/apply in a specified order&lt;/span&gt;
        &lt;span class="s"&gt;# Also, I generate an atlantis.yaml config &lt;/span&gt;
        &lt;span class="s"&gt;#   with terramate too, so I exclude it from run order: &lt;/span&gt;
        &lt;span class="s"&gt;#   it's just a yaml, not tf-code&lt;/span&gt;
        &lt;span class="s"&gt;- run: terramate list --run-order -c --no-tags atlantis -B origin/master &amp;gt; .tm-run-order&lt;/span&gt;
          &lt;span class="s"&gt;description: Get changed stacks&lt;/span&gt;

        &lt;span class="s"&gt;# And the obvious final step =)&lt;/span&gt;
        &lt;span class="s"&gt;- run: terramate generate&lt;/span&gt;
          &lt;span class="s"&gt;description: Generating tf code&lt;/span&gt;
      &lt;span class="s"&gt;# some other options, offtopic&lt;/span&gt;
      &lt;span class="s"&gt;id: github.com/XXX/YYY&lt;/span&gt;
      &lt;span class="s"&gt;apply_requirements: [mergeable, approved, undiverged]&lt;/span&gt;
      &lt;span class="s"&gt;delete_source_branch_on_merge: true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. (Optional) Generate &lt;code&gt;atlantis.yaml&lt;/code&gt; with Terramate too&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add this 'stack' to your Terramate repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="err"&gt;stack&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"atlantis"&lt;/span&gt;
  &lt;span class="py"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"atlantis"&lt;/span&gt;
  &lt;span class="py"&gt;id&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"your-uniq-id"&lt;/span&gt;

  &lt;span class="py"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;[&lt;/span&gt;
    &lt;span class="err"&gt;"atlantis"&lt;/span&gt;
  &lt;span class="err"&gt;]&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;

&lt;span class="err"&gt;generate_file&lt;/span&gt; &lt;span class="err"&gt;"atlantis.yaml"&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;condition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;(&lt;/span&gt;
    &lt;span class="c"&gt;# I place it in the root repo dir so I want to avoid 
&lt;/span&gt;    &lt;span class="c"&gt;#   file generation with child stacks, only with
&lt;/span&gt;    &lt;span class="c"&gt;#   atlantis stack and with not empty .tm-run-order
&lt;/span&gt;    &lt;span class="py"&gt;terramate.stack.name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;= "atlantis" &amp;amp;&amp;amp; tm_length(let.run_order) &amp;gt; 0&lt;/span&gt;
  &lt;span class="err"&gt;)&lt;/span&gt;

  &lt;span class="err"&gt;lets&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;run_order&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;tm_try(&lt;/span&gt;
      &lt;span class="err"&gt;tm_compact(tm_split("\n",&lt;/span&gt; &lt;span class="err"&gt;tm_trim(tm_file(".tm-run-order"),&lt;/span&gt; &lt;span class="err"&gt;"\n"))),&lt;/span&gt;
    &lt;span class="nn"&gt;[]&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;# dirty magic to fill config options by my folder structure
&lt;/span&gt;    &lt;span class="py"&gt;projects&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;[for x in let.run_order : {&lt;/span&gt;
      &lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;tm_replace(x, "/^terraform/(projectX/)?/", "")&lt;/span&gt;
      &lt;span class="py"&gt;dir&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;x&lt;/span&gt;
      &lt;span class="py"&gt;autoplan&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;# I want to trigger Atlantis for every project
&lt;/span&gt;        &lt;span class="c"&gt;#   because I already know that every project 
&lt;/span&gt;        &lt;span class="c"&gt;#   here is modified
&lt;/span&gt;        &lt;span class="py"&gt;when_modified&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;(&lt;/span&gt;
          &lt;span class="err"&gt;tm_substr(x,&lt;/span&gt; &lt;span class="err"&gt;0,&lt;/span&gt; &lt;span class="err"&gt;14)&lt;/span&gt; &lt;span class="err"&gt;==&lt;/span&gt; &lt;span class="err"&gt;"terraform/projectX/"&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="nn"&gt;["../../../**/*"]&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;["../**/*"]&lt;/span&gt;
        &lt;span class="err"&gt;)&lt;/span&gt;
        &lt;span class="py"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;false&lt;/span&gt;
      &lt;span class="err"&gt;}&lt;/span&gt;
    &lt;span class="err"&gt;}]&lt;/span&gt;
    &lt;span class="py"&gt;config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;{&lt;/span&gt;
      &lt;span class="py"&gt;version&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;3&lt;/span&gt;
      &lt;span class="py"&gt;automerge&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;true&lt;/span&gt;
      &lt;span class="py"&gt;parallel_plan&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;false&lt;/span&gt;
      &lt;span class="py"&gt;parallel_apply&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;false&lt;/span&gt;
      &lt;span class="py"&gt;projects&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;let.projects&lt;/span&gt;
    &lt;span class="err"&gt;}&lt;/span&gt;
  &lt;span class="err"&gt;}&lt;/span&gt;

  &lt;span class="py"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;tm_yamlencode(let.config)&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done! Now, for every PR, Atlantis will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Run pre-workflow hooks to generate Terraform code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;(Optional) Generate its own configuration to run projects in the desired order.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're looking for more tips and useful info, definitely swing by my blog post &lt;a href="https://dev.to/rgeraskin/terramate-zsh-4hbi"&gt;10 Useful Aliases and Functions for Terramate and Zsh Users&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>terraform</category>
      <category>atlantis</category>
      <category>terramate</category>
    </item>
    <item>
      <title>Terramate ❤️ Zsh</title>
      <dc:creator>Roman Geraskin</dc:creator>
      <pubDate>Tue, 26 Mar 2024 14:37:49 +0000</pubDate>
      <link>https://dev.to/rgeraskin/terramate-zsh-4hbi</link>
      <guid>https://dev.to/rgeraskin/terramate-zsh-4hbi</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/terramate-io/terramate" rel="noopener noreferrer"&gt;Terramate&lt;/a&gt; is an amazing tool to enhance your Terraform experience. However, there is a way to make it even more handy for everyday use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solve annoying things
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Too long to type
&lt;/h3&gt;

&lt;p&gt;Obvious one: &lt;code&gt;terraform&lt;/code&gt; and &lt;code&gt;terramate&lt;/code&gt; commands are &lt;strong&gt;too long to type&lt;/strong&gt;=)&lt;/p&gt;

&lt;p&gt;Use aliases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ~/.zshrc&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;tf&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"terraform "&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;tm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"terramate "&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;tmg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"terramate generate "&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You still have to type &lt;code&gt;terraform&lt;/code&gt; after &lt;code&gt;tm run&lt;/code&gt; because tm knows nothing about your aliases. But it will be solved below too.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Command chaining
&lt;/h3&gt;

&lt;p&gt;Often, to execute a Terraform command, you need to run a preceding command. For example, before &lt;code&gt;tf init&lt;/code&gt;, you should run &lt;code&gt;tm generate&lt;/code&gt;. Similarly, before &lt;code&gt;plan&lt;/code&gt;, you might need to run &lt;code&gt;init&lt;/code&gt;, and so on.&lt;/p&gt;

&lt;p&gt;You could use Makefiles or the &lt;code&gt;terramate script run&lt;/code&gt; feature that comes with Terramate to handle command chaining. However, this only addresses the issue of chaining commands. Plus, you'd have to add these configurations to every repository you work with.&lt;/p&gt;

&lt;p&gt;And it's still too much typing. You'll likely resort to using aliases anyway. So, why not start with shell configuration right from the start?&lt;/p&gt;

&lt;p&gt;Zsh functions can help:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ~/.zshrc&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;tmi &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  terramate generate &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  terramate run terraform init &lt;span class="nv"&gt;$@&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;tmp &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  tmi &lt;span class="nv"&gt;$1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  terramate run terraform plan &lt;span class="nt"&gt;-out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tfplan &lt;span class="nv"&gt;$@&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Tags for stacks
&lt;/h3&gt;

&lt;p&gt;Tags are a fantastic feature of Terramate, especially if you're managing many stacks. They allow all &lt;code&gt;tm run&lt;/code&gt; commands to be executed in stack directories selected by tags. However, their usability could be better—you always need to type them somewhere in the middle of a &lt;code&gt;tm&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;For example, if you want to check the &lt;code&gt;plan&lt;/code&gt; for the dev environment and then for the stage environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terramate run &lt;span class="nt"&gt;--tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mngt:dev terraform plan
terramate run &lt;span class="nt"&gt;--tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mngt:stage terraform plan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To create the last command, you can use a sequence of keys like this before typing &lt;code&gt;stage&lt;/code&gt;: up, option+left, option+left, left, esc, backspace.&lt;/p&gt;

&lt;p&gt;Make functions smarter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ~/.zshrc&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;tmi &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  terramate generate &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  terramate run &lt;span class="nt"&gt;--tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt; terraform init &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;:2&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;tmp &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  tmi &lt;span class="nv"&gt;$1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  terramate run &lt;span class="nt"&gt;--tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt; terraform plan &lt;span class="nt"&gt;-out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tfplan &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;:2&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, you have fewer steps before you start typing &lt;code&gt;stage&lt;/code&gt;: up, esc, backspace.&lt;/p&gt;

&lt;p&gt;Additionally, you can specify &lt;code&gt;plan&lt;/code&gt; options as the second or later arguments thanks to &lt;code&gt;${@:2}&lt;/code&gt; in the function.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Not sure about what you've typed?
&lt;/h3&gt;

&lt;p&gt;Print the command before executing it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ~/.zshrc&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;tmr &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  print &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"terramate run --tags=&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt; "&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So &lt;code&gt;tmr dev:asd ls&lt;/code&gt; + enter will lead to &lt;code&gt;terramate run --tags=dev:asd ls&lt;/code&gt; in the command line.     &lt;/p&gt;

&lt;h2&gt;
  
  
  Let's put it all together
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ~/.zshrc&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;tf&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"terraform "&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;tm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"terramate "&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;tmg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"terramate generate "&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;tmi &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  terramate generate &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  terramate run &lt;span class="nt"&gt;--tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt; terraform init &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;:2&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;tmv &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  tmi &lt;span class="nv"&gt;$1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  terramate run &lt;span class="nt"&gt;--tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt; terraform validate &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;:2&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;tmp &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  tmi &lt;span class="nv"&gt;$1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  terramate run &lt;span class="nt"&gt;--tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt; terraform plan &lt;span class="nt"&gt;-out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tfplan &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;:2&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;tma &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  print &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"terramate run --tags=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt; terraform apply tfplan &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;:2&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;tmpl &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  tmi &lt;span class="nv"&gt;$1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  terramate run &lt;span class="nt"&gt;--tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt; terraform providers lock &lt;span class="nt"&gt;-platform&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;linux_amd64 &lt;span class="nt"&gt;-platform&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;darwin_amd64 &lt;span class="nt"&gt;-platform&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;darwin_arm64 &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;:2&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;tmc &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  tmi &lt;span class="nv"&gt;$1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  terramate run &lt;span class="nt"&gt;--tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt; terraform console &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;:2&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;tmr &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  print &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"terramate run --tags=&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt; "&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Examples
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tmi admin        &lt;span class="c"&gt;# will run `terramate run --tags=admin terraform init`&lt;/span&gt;
tmp dev:infra    &lt;span class="c"&gt;# will run `terramate run --tags=dev:infra terraform plan -out=tfplan`&lt;/span&gt;
tmr admin        &lt;span class="c"&gt;# will print `terramate run --tags=admin` in prompt so you can run any command in the admin stack dir&lt;/span&gt;
tmr admin &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; &lt;span class="c"&gt;# will print `terramate run --tags=admin ls -la` in prompt, press 'enter' to execute `ls -la` in the admin stack dir or append more args&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Shortcuts description
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tf&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;terraform&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;terramate&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tmg&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;terramate generate&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;tmi &amp;lt;TAGS&amp;gt;&lt;/code&gt; [ARGS]&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;tm generate&lt;/code&gt; =&amp;gt; &lt;code&gt;tf init [ARGS]&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;tmv &amp;lt;TAGS&amp;gt;&lt;/code&gt; [ARGS]&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;tm generate&lt;/code&gt; =&amp;gt; &lt;code&gt;tf init&lt;/code&gt; =&amp;gt; &lt;code&gt;tf validate [ARGS]&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;tmp &amp;lt;TAGS&amp;gt;&lt;/code&gt; [ARGS]&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;tm generate&lt;/code&gt; =&amp;gt; &lt;code&gt;tf init&lt;/code&gt; =&amp;gt; &lt;code&gt;tf plan -out=tfplan [ARGS]&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;tma &amp;lt;TAGS&amp;gt;&lt;/code&gt; [ARGS]&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tf apply tfplan [ARGS]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;tmpl &amp;lt;TAGS&amp;gt;&lt;/code&gt; [ARGS]&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;tm generate&lt;/code&gt; =&amp;gt; &lt;code&gt;tf init&lt;/code&gt; =&amp;gt; &lt;code&gt;tf providers lock -platform=linux_amd64 -platform=darwin_amd64 -platform=darwin_arm64 [ARGS]&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;tml &amp;lt;TAGS&amp;gt;&lt;/code&gt; [ARGS]&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;tm generate&lt;/code&gt; =&amp;gt; &lt;code&gt;tf init&lt;/code&gt; =&amp;gt; &lt;code&gt;tf console [ARGS]&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;tmc &amp;lt;TAGS&amp;gt;&lt;/code&gt; [ARGS]&lt;/td&gt;
&lt;td&gt;&lt;code&gt;aws sso login [ARGS]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tmr &amp;lt;TAGS&amp;gt; &amp;lt;CMD&amp;gt; [ARGS]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;terramate run --tags=&amp;lt;TAGS&amp;gt; &amp;lt;CMD&amp;gt; [ARGS]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Bonus
&lt;/h2&gt;

&lt;p&gt;Use different environment variable values for different stacks.&lt;/p&gt;

&lt;p&gt;For example, if stacks use different AWS accounts, place the account name in the stack's globals and add it to your repository's root tm-file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# repo root terramate.tm&lt;/span&gt;
terramate &lt;span class="o"&gt;{&lt;/span&gt;
  config &lt;span class="o"&gt;{&lt;/span&gt;
    run &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;env&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        AWS_PROFILE &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;global&lt;/span&gt;&lt;span class="p"&gt;.aws_profile&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;tmr aws aws sso login&lt;/code&gt; FTW 🤟&lt;/p&gt;

</description>
      <category>devops</category>
      <category>terraform</category>
      <category>zsh</category>
      <category>terramate</category>
    </item>
    <item>
      <title>Get a specific apiVersion manifest from k8s</title>
      <dc:creator>Roman Geraskin</dc:creator>
      <pubDate>Tue, 19 Mar 2024 16:53:38 +0000</pubDate>
      <link>https://dev.to/rgeraskin/get-a-specific-apiversion-manifest-from-k8s-385j</link>
      <guid>https://dev.to/rgeraskin/get-a-specific-apiversion-manifest-from-k8s-385j</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;To get a manifest from k8s in any supported API version you can use an extended &lt;code&gt;kubectl get&lt;/code&gt; notation like &lt;code&gt;kubectl get deployments.v1beta1.extensions mydeploy -o yaml&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Briefly and with examples
&lt;/h2&gt;

&lt;p&gt;Everybody knows how to get a resource manifest from Kubernetes. But do you know that you can put a manifest with one &lt;code&gt;apiVersion&lt;/code&gt; set and get the same resource manifest with another &lt;code&gt;apiVersion&lt;/code&gt; back?&lt;/p&gt;

&lt;h3&gt;
  
  
  Imagine
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;You have a &lt;code&gt;deployment&lt;/code&gt; in a cluster, that uses &lt;code&gt;api-version extensions/v1beta1&lt;/code&gt; from a git repo.&lt;/li&gt;
&lt;li&gt;You've updated the cluster (1.15 =&amp;gt; 1.16). Old deployment works fine, but you can't deploy nothing new with the old manifest because in k8s 1.16 &lt;code&gt;api-version extensions/v1beta1&lt;/code&gt; is absent.&lt;/li&gt;
&lt;li&gt;You have two options:

&lt;ol&gt;
&lt;li&gt;Rewrite it manually or&lt;/li&gt;
&lt;li&gt;Get it from the cluster in updated spec format (&lt;code&gt;apps/v1&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;p&gt;To get manifest from k8s you can:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;below there is an example for v1.15, where extensions/v1beta1 is not removed yet&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# from extensions&lt;/span&gt;
kubectl get deployments.extensions ext &lt;span class="nt"&gt;-o&lt;/span&gt; yaml
&lt;span class="c"&gt;# from extensions, but for v1beta1&lt;/span&gt;
kubectl get deployments.v1beta1.extensions ext &lt;span class="nt"&gt;-o&lt;/span&gt; yaml
&lt;span class="c"&gt;# from apps&lt;/span&gt;
kubectl get deployments.apps ext &lt;span class="nt"&gt;-o&lt;/span&gt; yaml
&lt;span class="c"&gt;# from apps, but v1&lt;/span&gt;
kubectl get deployments.v1.apps ext &lt;span class="nt"&gt;-o&lt;/span&gt; yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that if you do just &lt;code&gt;kubectl get deployments ext -o yaml&lt;/code&gt;, you will get a manifest from &lt;code&gt;extensions/v1beta1&lt;/code&gt; nevertheless you've even applied &lt;code&gt;apps/v1&lt;/code&gt; before. Details &lt;a href="https://github.com/kubernetes/kubernetes/issues/58131#issuecomment-356823588" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prove it
&lt;/h2&gt;

&lt;p&gt;1) Start 1.15&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   minikube start &lt;span class="nt"&gt;--kubernetes-version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;v1.15.12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2) Make 2 manifests for &lt;code&gt;deployments&lt;/code&gt; with different versions. Note the absence of &lt;code&gt;spec.selector&lt;/code&gt; in &lt;code&gt;extensions/v1beta1&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;   &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
   &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
   &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;creationTimestamp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
     &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps&lt;/span&gt;
     &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps&lt;/span&gt;
   &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
     &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps&lt;/span&gt;
     &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="na"&gt;creationTimestamp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
         &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
           &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps&lt;/span&gt;
       &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
           &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps&lt;/span&gt;
   &lt;span class="s"&gt;---&lt;/span&gt;
   &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;extensions/v1beta1&lt;/span&gt;
   &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
   &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ext&lt;/span&gt;
     &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ext&lt;/span&gt;
   &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
     &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="na"&gt;creationTimestamp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
         &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
           &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ext&lt;/span&gt;
       &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
           &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ext&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3) Apply&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   ❯ minikube kubectl &lt;span class="nt"&gt;--&lt;/span&gt; apply &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
   deployment.apps/apps created
   deployment.extensions/ext created
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4) Check that resources &lt;code&gt;apps&lt;/code&gt; and &lt;code&gt;ext&lt;/code&gt; are in &lt;code&gt;extensions/v1beta1&lt;/code&gt; and in &lt;code&gt;apps/v1&lt;/code&gt; too&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The hard way - curl:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   ❯ minikube kubectl &lt;span class="nt"&gt;--&lt;/span&gt; proxy
   Starting to serve on 127.0.0.1:8001
   ❯ curl &lt;span class="nt"&gt;-s&lt;/span&gt; 127.0.0.1:8001/apis/extensions/v1beta1/namespaces/default/deployments/apps | yq &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--yaml-output&lt;/span&gt;
   kind: Deployment
   apiVersion: extensions/v1beta1
   &lt;span class="c"&gt;# ...&lt;/span&gt;
   ❯ curl &lt;span class="nt"&gt;-s&lt;/span&gt; 127.0.0.1:8001/apis/extensions/v1beta1/namespaces/default/deployments/ext | yq &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--yaml-output&lt;/span&gt;
   kind: Deployment
   apiVersion: extensions/v1beta1
   &lt;span class="c"&gt;# ...&lt;/span&gt;
   ❯ curl &lt;span class="nt"&gt;-s&lt;/span&gt; 127.0.0.1:8001/apis/apps/v1/namespaces/default/deployments/apps | yq &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--yaml-output&lt;/span&gt;
   kind: Deployment
   apiVersion: apps/v1
   &lt;span class="c"&gt;# ...&lt;/span&gt;
   ❯ curl &lt;span class="nt"&gt;-s&lt;/span&gt; 127.0.0.1:8001/apis/apps/v1/namespaces/default/deployments/ext | yq &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--yaml-output&lt;/span&gt;
   kind: Deployment
   apiVersion: apps/v1
   &lt;span class="c"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Or kubectl:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="c"&gt;# from extensions&lt;/span&gt;
   kubectl get deployments.extensions ext &lt;span class="nt"&gt;-o&lt;/span&gt; yaml
   &lt;span class="c"&gt;# from extensions, but v1beta1&lt;/span&gt;
   kubectl get deployments.v1beta1.extensions ext &lt;span class="nt"&gt;-o&lt;/span&gt; yaml
   &lt;span class="c"&gt;# from apps&lt;/span&gt;
   kubectl get deployments.apps ext &lt;span class="nt"&gt;-o&lt;/span&gt; yaml
   &lt;span class="c"&gt;# from apps, but v1&lt;/span&gt;
   kubectl get deployments.v1.apps ext &lt;span class="nt"&gt;-o&lt;/span&gt; yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bonus: kubectl explain
&lt;/h2&gt;

&lt;p&gt;If you do &lt;code&gt;kubectl explain deployment&lt;/code&gt; than (surprise!) you'll get a description for &lt;code&gt;extensions/v1beta1&lt;/code&gt;. Because &lt;code&gt;kubectl explain&lt;/code&gt; &lt;a href="https://github.com/kubernetes/kubernetes/issues/73062" rel="noopener noreferrer"&gt;works the same way&lt;/a&gt;, just like &lt;code&gt;kubectl get&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;If you want a specific version, use an &lt;code&gt;--api-version&lt;/code&gt; flag :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;minikube kubectl &lt;span class="nt"&gt;--&lt;/span&gt; explain deployment.spec &lt;span class="nt"&gt;--api-version&lt;/span&gt; apps/v1
minikube kubectl &lt;span class="nt"&gt;--&lt;/span&gt; explain deployment.spec &lt;span class="nt"&gt;--api-version&lt;/span&gt; extensions/v1beta1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>k8s</category>
    </item>
  </channel>
</rss>
