<?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: Scott McAllister</title>
    <description>The latest articles on DEV Community by Scott McAllister (@stmcallister).</description>
    <link>https://dev.to/stmcallister</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%2F21635%2F3a84165e-de65-4544-9a45-b35756d66526.jpeg</url>
      <title>DEV Community: Scott McAllister</title>
      <link>https://dev.to/stmcallister</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/stmcallister"/>
    <language>en</language>
    <item>
      <title>Building a web server: Containers</title>
      <dc:creator>Scott McAllister</dc:creator>
      <pubDate>Fri, 07 Jun 2024 19:47:30 +0000</pubDate>
      <link>https://dev.to/stmcallister/building-a-home-web-server-containerizing-our-app-50ed</link>
      <guid>https://dev.to/stmcallister/building-a-home-web-server-containerizing-our-app-50ed</guid>
      <description>&lt;p&gt;Welcome back to This Old Box! A series that covers the journey of building and running a web server out of an old 2014 Mac mini. &lt;/p&gt;

&lt;p&gt;In this post we'll walk through how we containerized our first web application, configured our Kubernetes cluster with our new container, and exposed that cluster to the world.&lt;/p&gt;

&lt;p&gt;The project we deployed is the most simple from an infrastructure standpoint. It's a static site that is mainly html, css, and a wee bit of client-side JavaScript. Logistically, the whole site fit into a single container. &lt;/p&gt;

&lt;h2&gt;
  
  
  Docker
&lt;/h2&gt;

&lt;p&gt;There are several container runtimes in the industry today. We chose   to use &lt;a href="https://www.docker.com/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt; as it seems to be the industry standard, especially on a Mac. Docker is also well documented, and thus should be the easiest to learn. Although, I am concerned because I've heard the runtime is a bit greedy with CPU and memory resources. I hope this Old Box can handle it.&lt;/p&gt;

&lt;p&gt;We installed &lt;a href="https://www.docker.com/products/docker-desktop/" rel="noopener noreferrer"&gt;Docker Desktop&lt;/a&gt; in &lt;a href="https://dev.to/stmcallister/building-a-web-server-installing-the-software-gjb"&gt;our last post&lt;/a&gt;. With that application running we built a docker image by writing the instructions in a &lt;code&gt;Dockerfile&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;This first website we containerized is a collection of static sites that my son created called &lt;a href="https://www.ajmcweb.com/" rel="noopener noreferrer"&gt;AjMcWeb&lt;/a&gt;. The instructions for building the container for this site were relatively simple. All we really needed to do was copy all the html, javascript, css, and image files for the site into the container, run them on &lt;code&gt;nginx&lt;/code&gt;, and expose port 3001 on the container. The &lt;code&gt;Dockerfile&lt;/code&gt; to accomplish these steps looked like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; nginx&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . /usr/share/nginx/html&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3001&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using that &lt;code&gt;Dockerfile&lt;/code&gt;, we ran the following &lt;code&gt;docker build&lt;/code&gt; command inside the same folder to build a running image of that container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; ajmcweb:0.1 &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command built the Docker image. The &lt;code&gt;-t&lt;/code&gt; argument told Docker to tag the image with the the name &lt;code&gt;ajmcweb&lt;/code&gt; and gave it a version of &lt;code&gt;0.1&lt;/code&gt;. The trailing &lt;code&gt;.&lt;/code&gt; told Docker to use the Dockerfile in the current directory.&lt;/p&gt;

&lt;p&gt;Now that we had our site encapsulated inside a Docker container we needed to deploy it to the world. That's where Kubernetes came in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kubernetes
&lt;/h2&gt;

&lt;p&gt;Docker Desktop includes a standalone Kubernetes server and client that we enabled &lt;a href="https://dev.to/stmcallister/building-a-web-server-installing-the-software-gjb"&gt;in our last post&lt;/a&gt;. This enablement instantiated the images required to run the Kubernetes server as containers, and installed the &lt;code&gt;kubectl&lt;/code&gt; utility--used to manage our Kubernetes cluster--on our machine.    &lt;/p&gt;

&lt;h2&gt;
  
  
  ngrok for ingress
&lt;/h2&gt;

&lt;p&gt;Before configuring our Kubernetes cluster, we needed to install the &lt;a href="https://github.com/ngrok/kubernetes-ingress-controller?tab=readme-ov-file#ngrok-kubernetes-ingress-controller" rel="noopener noreferrer"&gt;ngrok Ingress Controller&lt;/a&gt; so we could use &lt;a href="https://ngrok.com/" rel="noopener noreferrer"&gt;ngrok&lt;/a&gt; to provide ingress to our application. &lt;a href="https://kubernetes.io/docs/concepts/services-networking/ingress/" rel="noopener noreferrer"&gt;Ingress&lt;/a&gt; is "an API object that manages external access to the services in a cluster." &lt;/p&gt;

&lt;p&gt;ngrok is an ingress platform that helps provide access to a variety of things including Kubernetes clusters as well as devices and other networks. The reason why we chose ngrok for this particular project is because &lt;a href="https://ngrok.com/blog-post/ngrok-ingress-controller-differentiators#the-ngrok-kubernetes-ingress-controller-works-behind-nat" rel="noopener noreferrer"&gt;ngrok works behind a NAT&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here's how we set up ngrok and its ingress controller. &lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up ngrok
&lt;/h2&gt;

&lt;p&gt;First, we signed up for a &lt;a href="https://ngrok.com/signup" rel="noopener noreferrer"&gt;free ngrok&lt;/a&gt; and then upgraded to a &lt;a href="https://ngrok.com/pricing" rel="noopener noreferrer"&gt;paid plan&lt;/a&gt; so we could use our &lt;a href="https://ngrok.com/docs/guides/how-to-set-up-a-custom-domain/" rel="noopener noreferrer"&gt;own domains&lt;/a&gt; for our sites--like &lt;a href="https://www.ajmcweb.com/" rel="noopener noreferrer"&gt;www.ajmcweb.com&lt;/a&gt; for example.&lt;/p&gt;

&lt;p&gt;In order to use ngrok we first needed to get our &lt;a href="https://ngrok.com/docs/agent/#authtokens" rel="noopener noreferrer"&gt;ngrok authtoken&lt;/a&gt; in our ngrok dashboard. Then, we used that to set an &lt;code&gt;NGROK_AUTHTOKEN&lt;/code&gt; environment variable on our system.&lt;/p&gt;

&lt;p&gt;Next we &lt;a href="https://ngrok.com/docs/agent/#api-keys" rel="noopener noreferrer"&gt;created an API Key&lt;/a&gt; and set that to an &lt;code&gt;NGROK_API_KEY&lt;/code&gt; environment variable.&lt;/p&gt;

&lt;p&gt;The last environment variable we set was &lt;code&gt;NAMESPACE&lt;/code&gt;. This is going to be the Kubernetes namespace we'll use for our clusters on this box. In our case, we used &lt;code&gt;macbox&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Both of these environment variables will be used when we install the ngrok ingress controller. But, before we leave ngrok, I'll mention how we set up our own domains inside of our ngrok account. &lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up custom domains in ngrok
&lt;/h3&gt;

&lt;p&gt;We followed the &lt;a href="https://ngrok.com/docs/guides/how-to-set-up-a-custom-domain/" rel="noopener noreferrer"&gt;ngrok guide for setting up Custom Domains&lt;/a&gt; which can be boiled down to two steps. First, we went to the Domains section of the &lt;a href="https://dashboard.ngrok.com" rel="noopener noreferrer"&gt;ngrok dashboard&lt;/a&gt; and clicked the New Domain button. &lt;/p&gt;

&lt;p&gt;We typed in &lt;code&gt;www.ajmcweb.com&lt;/code&gt; and clicked Continue. This gave us a Domain Record value that we copied and pasted into a CNAME record on our domain registrar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up ngrok ingress controller
&lt;/h2&gt;

&lt;p&gt;To establish ingress, or provide user access, we wanted to incorporate the ngrok account that we configured previously. ngrok has an &lt;a href=""&gt;ingress controller for Kubernetes&lt;/a&gt; which we installed as a helm chart with the following steps:&lt;/p&gt;

&lt;p&gt;We added the ngrok repo to our helm settings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm repo add ngrok https://ngrok.github.io/kubernetes-ingress-controller
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we installed the helm chart with the following command, referencing the environment variables we set earlier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm &lt;span class="nb"&gt;install &lt;/span&gt;ngrok-ingress-controller ngrok/kubernetes-ingress-controller &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--namespace&lt;/span&gt; &lt;span class="nv"&gt;$NAMESPACE&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--create-namespace&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set&lt;/span&gt; credentials.apiKey&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$NGROK_API_KEY&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set&lt;/span&gt; credentials.authtoken&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$NGROK_AUTHTOKEN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configuring Kubernetese
&lt;/h3&gt;

&lt;p&gt;Going back to our code we captured in a Docker container we were ready to define our application deployment in Kubernetes. We began with configuring a &lt;code&gt;Service&lt;/code&gt; that is a pod running on &lt;code&gt;http&lt;/code&gt; and port 80.&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;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;ajmcweb&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;macbox&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&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;80&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;ajmcweb&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we defined a &lt;code&gt;Deployment&lt;/code&gt; object to run our &lt;code&gt;ajmcweb&lt;/code&gt; container we built earlier. In the code below you'll see that we're running version &lt;code&gt;0.3&lt;/code&gt; of that container, which is denoted as &lt;code&gt;ajmcweb:0.3&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="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;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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ajmcweb&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;macbox&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;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ajmcweb&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;labels&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;ajmcweb&lt;/span&gt;
        &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v0.1&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ajmcweb&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;ajmcweb:0.3&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
            &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And, finally, we configured our ingress object using the &lt;a href="https://ngrok.com/docs/k8s/" rel="noopener noreferrer"&gt;ngrok Kubernetes Operator&lt;/a&gt; that we installed earlier.&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="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;networking.k8s.io/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;Ingress&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;ajmcweb&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;macbox&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;ingressClassName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ngrok&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;www.ajmcweb.com&lt;/span&gt;
    &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
        &lt;span class="na"&gt;pathType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prefix&lt;/span&gt;
        &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;service&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;ajmcweb&lt;/span&gt;
            &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our ingress object had one rule which defined the host as &lt;code&gt;www.ajmcweb.com&lt;/code&gt; and the backend would run the &lt;code&gt;ajmcweb&lt;/code&gt; service on port 80.&lt;/p&gt;

&lt;p&gt;We applied those Kubernetes configurations with a &lt;code&gt;kubectl apply -f &amp;lt;filename&amp;gt;.yaml&lt;/code&gt; and &lt;a href="https://www.ajmcweb.com" rel="noopener noreferrer"&gt;www.ajmcweb.com&lt;/a&gt; came right up in the browser!&lt;/p&gt;

&lt;p&gt;Success!&lt;/p&gt;

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

&lt;p&gt;This felt great to be able to get our simple static website deployed on our server and have ngrok handle the network settings to make the site reachable. &lt;/p&gt;

&lt;p&gt;Our next task will be to get a more complex application online. My son has an application he built to help track the time he spends practicing his saxophone. The app uses &lt;a href="https://nodejs.org/en" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; and &lt;a href="https://www.mysql.com/" rel="noopener noreferrer"&gt;MySQL&lt;/a&gt; for the backend and has a much more rich frontend. It will require some more effort both from a deployment standpoint and from a processing perspective. Stay tuned as we figure out how to get this application online and keep it running!&lt;/p&gt;

</description>
      <category>docker</category>
      <category>kubernetes</category>
      <category>helm</category>
      <category>learning</category>
    </item>
    <item>
      <title>Building a web server: Installing the right software</title>
      <dc:creator>Scott McAllister</dc:creator>
      <pubDate>Wed, 17 Jan 2024 07:42:21 +0000</pubDate>
      <link>https://dev.to/stmcallister/building-a-web-server-installing-the-software-gjb</link>
      <guid>https://dev.to/stmcallister/building-a-web-server-installing-the-software-gjb</guid>
      <description>&lt;p&gt;Welcome back to This Old Box! A series where I chronicle the journey of building a web server out of an old 2014 Mac mini. My son and I both have web applications that are running on various platforms, and we want to learn more about how to set up the infrastructure to serve those applications. &lt;/p&gt;

&lt;p&gt;In our previous posts we initialized our machine and set up a KVM switch to share hardware--keyboard, mouse, and monitor--with my newer Mac mini that is the current family machine. &lt;/p&gt;

&lt;h2&gt;
  
  
  Homebrew
&lt;/h2&gt;

&lt;p&gt;In this post we're going to prepare the software to run our web server. As is the case when I set up most Macs, my first install is the &lt;a href="https://brew.sh/" rel="noopener noreferrer"&gt;Homebrew&lt;/a&gt; package manager. They have instructions on their site for how to install, and once you have Homebrew set up most of the rest of your software can be installed with a &lt;code&gt;brew install &amp;lt;package_name&amp;gt;&lt;/code&gt; in your Terminal. With the exception of our next piece of software.   &lt;/p&gt;

&lt;h2&gt;
  
  
  Docker Desktop
&lt;/h2&gt;

&lt;p&gt;I've dabbled with containers and Kubernetes in the past, but have never run anything in a production environment. Since my container experience has been with &lt;a href="https://www.docker.com/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt; we're going to install &lt;a href="https://docs.docker.com/desktop/install/mac-install/" rel="noopener noreferrer"&gt;Docker Desktop&lt;/a&gt;. It's the only option for installing Docker on a Mac. And, there's an option to also &lt;a href="https://docs.docker.com/desktop/kubernetes/" rel="noopener noreferrer"&gt;enable Kubernetes&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitLab Setup
&lt;/h2&gt;

&lt;p&gt;The code for my son's projects is in &lt;a href="https://gitlab.com/" rel="noopener noreferrer"&gt;GitLab&lt;/a&gt;. Our next step was to &lt;a href=""&gt;generate SSH keys&lt;/a&gt; to securely work interact with his repositories. &lt;/p&gt;

&lt;p&gt;To set the keys for your account you'll go to the &lt;code&gt;User Settings / Edit Profile&lt;/code&gt; page. I found it by selecting my profile image in the left sidebar, and then clicking on my name at the top. This got me to User Settings. On that page you'll see the &lt;code&gt;Edit Profile&lt;/code&gt; button on the right side of the header. Then, on the Profile page, you'll see the SSH Keys option on the left sidebar. &lt;/p&gt;

&lt;p&gt;If you're new to SSH keys, or just need a refresher, GitLab provides a great resource called &lt;a href="https://docs.gitlab.com/ee/user/ssh.html" rel="noopener noreferrer"&gt;Use SSH Keys to Communicate with GitLab&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With our SSH keys in place we were ready to clone my son's projects onto our machine. We wanted to be fancy and perform the cloning using the Terminal. To clone a repo you use the following command in the directory where you want the code to live:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone git@gitlab.com:account_name/repo_name.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the topic of directory structure we've kept our server very basic. To keep with Mac conventions we decided to keep all our files in the &lt;code&gt;~/Documents&lt;/code&gt; folder. For my son's projects we're going to be running the infrastructure with Kubernetes, so we named the directory for all his code &lt;code&gt;phippy&lt;/code&gt;. With code cloned to our machine we wanted to have just a single GUI-based code editor. We chose &lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;VS Code&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  VS Code
&lt;/h2&gt;

&lt;p&gt;We wanted this machine to be as lean as possible. There is only so much memory and processing power to go around. Remember, our machine has 3 GHz Dual-Core Intel Core i7 processor with 16 GB 1600 MHz DDR3 memory. We also wanted as much of that space and power to be used for serving up our web applications. However, we also wanted to have an additional option for editing any code files, in addition to &lt;a href="https://www.vim.org/" rel="noopener noreferrer"&gt;vim&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://code.visualstudio.com/download" rel="noopener noreferrer"&gt;VS Code&lt;/a&gt; has been my defacto GUI code editor for years, mainly because Microsoft has really nailed the lean part right when it comes to this application. It runs quite smoothly and efficiently.&lt;/p&gt;

&lt;p&gt;In addition to installing the main application, we also enabled the &lt;a href="https://code.visualstudio.com/docs/editor/command-line" rel="noopener noreferrer"&gt;VS Code CLI&lt;/a&gt;. This allows us to have more control with how we open VS Code. We can open files or whole directories through the CLI options. It's how I usually open my VS Code sessions on every machine I use.&lt;/p&gt;

&lt;p&gt;Those are the applications we initially installed to get our projects running. We may install more in the future, but up until now this is the application stack we wanted to use. &lt;/p&gt;

&lt;p&gt;In our next post we go over how we created our containers, configured our Kubernetes cluster, and exposed our web applications to the world. &lt;/p&gt;

</description>
      <category>learning</category>
      <category>development</category>
      <category>beginners</category>
      <category>infrastructure</category>
    </item>
    <item>
      <title>This Old Box: Setting Up the KVM Switch</title>
      <dc:creator>Scott McAllister</dc:creator>
      <pubDate>Sun, 17 Dec 2023 09:17:57 +0000</pubDate>
      <link>https://dev.to/stmcallister/setting-up-the-kvm-switch-396n</link>
      <guid>https://dev.to/stmcallister/setting-up-the-kvm-switch-396n</guid>
      <description>&lt;p&gt;Welcome back to my series where I am &lt;a href="https://dev.to/stmcallister/building-my-own-application-server-post-1-14fd"&gt;building my own application server&lt;/a&gt; out of an old 2014 Mac mini. &lt;/p&gt;

&lt;p&gt;In my last post, I reformatted the hard drive of my retired 2014 Mac mini so I could start with a clean system. My family's current Mac mini is still running on the same desk. So, with two Mac minis running in a limited space I needed a solution for having both machines sharing the same peripheral hardware--monitor, keyboard, and mouse--without the need of unplugging cables between the machines.&lt;/p&gt;

&lt;p&gt;To make the switching between machines easy, we purchased this &lt;a href="https://www.amazon.com/gp/product/B09SCSKV1R/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&amp;amp;psc=1" rel="noopener noreferrer"&gt;KVM Switch&lt;/a&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%2Fgithub.com%2Fstmcallister%2Fthis-old-box%2Fassets%2F4098%2Fd282e2e7-f686-4c2f-b86e-44ee0760a17f" 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%2Fgithub.com%2Fstmcallister%2Fthis-old-box%2Fassets%2F4098%2Fd282e2e7-f686-4c2f-b86e-44ee0760a17f" alt="kvm-switch" width="720" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We picked this one because it appeared easy to set up to accomplish what we needed. Looking at the picture below, you can see the clearly labeled spots for Blue and Green. Those are the HDMI and USB ports for each of the machines you'll be switching between. Those cables come with the KVM switch, which was nice.&lt;/p&gt;

&lt;p&gt;The cables coming out of the bottom are the HDMI cable going to my monitor and the USB cable going to my USB hub. The 4-port hub (not pictured) is where I connect the keyboard and mouse to be used by both machines. &lt;/p&gt;

&lt;p&gt;Switching between machines only requires a push of the round button ringed by colored light. The color changes depending on which machine you're currently using. To keep things straight, we put Post-It notes on each box listing the color and either "New" or "Old". &lt;/p&gt;

&lt;p&gt;The setup of the switch was relatively straight forward, without a lot of hiccups. However, if you have any issues or questions please post them in the comments. We'd love to help!&lt;/p&gt;

&lt;p&gt;In our next post, we'll talk about all the software we installed on our server to run and serve our web applications. &lt;/p&gt;

</description>
      <category>beginners</category>
      <category>operations</category>
      <category>learning</category>
    </item>
    <item>
      <title>Learning ngrok: Inspect and Replay</title>
      <dc:creator>Scott McAllister</dc:creator>
      <pubDate>Thu, 16 Feb 2023 22:46:36 +0000</pubDate>
      <link>https://dev.to/stmcallister/learning-ngrok-inspect-and-replay-14ge</link>
      <guid>https://dev.to/stmcallister/learning-ngrok-inspect-and-replay-14ge</guid>
      <description>&lt;p&gt;My first post on Dev.to, written back in 2019, was about &lt;a href="https://dev.to/stmcallister/create-a-quick-local-web-server-with-python-and-ngrok-k0"&gt;creating a local web server using Python and ngrok&lt;/a&gt;. Little did I know that four years later I would be an &lt;a href="https://www.linkedin.com/posts/ngrok_werehiring-ngrok-activity-7032015967375282176-fs3g?utm_source=share&amp;amp;utm_medium=member_desktop" rel="noopener noreferrer"&gt;crew member&lt;/a&gt; on the rocket ship that is ngrok.&lt;/p&gt;

&lt;p&gt;I joined the company a couple weeks ago, and I'm in the middle of ramping up with the organization and learning more about the product. While I explore the features that are new to me I'm going to write about things I learn that may be valuable to the community. This post is going to cover the &lt;a href="https://blog.ngrok.com/posts/how-to-inspect-log-replay-traffic-with-ngrok" rel="noopener noreferrer"&gt;Inspect and Replay&lt;/a&gt; feature.&lt;/p&gt;

&lt;p&gt;A popular use case of ngrok is to receive webhooks on your local machine. As explained in the post I mentioned above, you can spin up a lightweight web server and run &lt;code&gt;ngrok http 80&lt;/code&gt; on your command line to provide a destination for incoming webhooks to land. For this exercise, I wrote the following code in &lt;a href="https://go.dev/" rel="noopener noreferrer"&gt;Go&lt;/a&gt; to receive webhooks at the root endpoint of the domain.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Webhook: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;":80"&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server is listening for requests on port 80, and prints the raw &lt;code&gt;http.Request&lt;/code&gt; which ends up looking something like this for a webhook received from &lt;a href="https://developer.pagerduty.com/docs/db0fa8c8984fc-overview" rel="noopener noreferrer"&gt;PagerDuty&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;Webhook&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Accept&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
 &lt;span class="n"&gt;Accept&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;gzip&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1438&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;PagerDuty&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Webhook&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;V3&lt;/span&gt;&lt;span class="m"&gt;.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Forwarded&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;For&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;44.222.99.999&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Forwarded&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Proto&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Pagerduty&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Signature&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;e3d475277secret24919127safe26759d684anotreal6285b25c4e15a89b7ccb9&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Webhook&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;eec61a23&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;d1af&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="n"&gt;b1b&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="n"&gt;fe3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;afea6fdead49&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Webhook&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Subscription&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;PXXXXX&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="m"&gt;0x140001582c0&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;1438&lt;/span&gt; 
&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt; &lt;span class="n"&gt;tunnel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ngrokpaperscissors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 
&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;57318&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0x14000158300&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is definitely more I could do to my code to get a better view of the incoming webhook. However, it appears ngrok already does a lot of that for us out the box. I've recently discovered the &lt;a href="https://ngrok.com/docs/secure-tunnels/ngrok-agent/web-inspection-interface?utm_medium=email&amp;amp;_hsmi=238290781&amp;amp;_hsenc=p2ANqtz-_u2UNVPIhd2gDqOvrq54-_ONDvd5JFDJm0Jx9_5aZhd8tgdfzUkI4SiQ_weScA1yawac7aDlv5cj2O3x2TFylGbaxGWw&amp;amp;utm_content=238290781&amp;amp;utm_source=hs_automation" rel="noopener noreferrer"&gt;ngrok inspector&lt;/a&gt;. It can be accessed while ngrok is running by opening &lt;a href="http://localhost:4040/" rel="noopener noreferrer"&gt;http://localhost:4040/&lt;/a&gt; where you're shown a view like this.&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%2F6a69r1lrvxlrmvi5npqi.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%2F6a69r1lrvxlrmvi5npqi.png" alt="Screenshot of ngrok inspector" width="800" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All the HTTP requests are listed down the left side, and on right is a nicely formatted representation of the webhook JSON payload. There are also tabs to look at the Headers, the Raw request, and even the Binary (though, I'm not entirely sure why I would want that :thinking_face: If you know, please share in the comments).&lt;/p&gt;

&lt;p&gt;There's also a Replay button that not only lets me replay the selected HTTP request, but you can also choose to modify the replayed request. For example, in my current situation I'm consuming webhooks from PagerDuty incidents. The webhooks are fired depending on whether the state of an incident changes. Some examples of incident state include triggered, acknowledged, and resolved. If my code that's consuming these webhooks behaves differently depending on what type of action occurring on the incident then to speed up my debugging I could select &lt;code&gt;Replay with Modifications&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fydbkgidqoietr7zpmnhm.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%2Fydbkgidqoietr7zpmnhm.png" alt="Screenshot of the Replay with Modifications button in the ngrok inspector" width="800" height="319"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Find the &lt;code&gt;event_type&lt;/code&gt; in the JSON body of the request and change it from &lt;code&gt;incident.triggered&lt;/code&gt; to &lt;code&gt;incident.acknowledged&lt;/code&gt; and click Replay.&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%2Fzajgaqekv4bgvqcf7d5q.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%2Fzajgaqekv4bgvqcf7d5q.png" alt="Screenshot of request modification window in the ngrok inspector" width="800" height="1254"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That seems pretty handy. I could see myself using that in the future. Hopefully you found this useful. If you have any questions or constructive suggestions please feel free to leave a comment. &lt;/p&gt;

</description>
      <category>gratitude</category>
    </item>
    <item>
      <title>Building My Own Application Server: Post 1</title>
      <dc:creator>Scott McAllister</dc:creator>
      <pubDate>Mon, 13 Feb 2023 06:08:17 +0000</pubDate>
      <link>https://dev.to/stmcallister/building-my-own-application-server-post-1-14fd</link>
      <guid>https://dev.to/stmcallister/building-my-own-application-server-post-1-14fd</guid>
      <description>&lt;p&gt;Remember when blogs first came out and most of them were written records of journeys folks encountered? Some embarked on adventurous travels, some were (and still are) a platform for sports fans to shout into the void, while others documented their progress in a particular project. This is more of the latter. I have a late 2014 Mac mini that has been cold on my desk for several months. We initially kept it arround just in case we needed something. But, I think it's safe to say that the just in case time period has adequately passed. &lt;/p&gt;

&lt;p&gt;Now my son and I want to turn this old Mac into a web server. We realize it would most definitely be easier to deploy our code onto one of the many application service providers (aka "clouds"), but what's the fun in that? While I'm a web application developer by trade, and my son has built his own &lt;a href="https://www.practacemusic.com/" rel="noopener noreferrer"&gt;web projects&lt;/a&gt;, neither of us have ever built and ran a production server before, so we're going to give it a shot. &lt;/p&gt;

&lt;p&gt;These posts are going to be a journal of our progress, our missteps, and our learnings.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Progress Update
&lt;/h2&gt;

&lt;p&gt;I reformatted harddrive on the &lt;a href="https://everymac.com/systems/apple/mac_mini/specs/mac-mini-core-i7-3.0-late-2014-specs.html" rel="noopener noreferrer"&gt;Mac mini (Late 2014)&lt;/a&gt;. It's running a 3 GHz Dual-Core Intel Core i7 processor with 16 GB 1600 MHz DDR3 memory. The machine contains a 2 TB Fusion Drive. My initial hypothesis is that the Fusion Drive is going to hurt our performance. But, hopefully, since we stripped the machine of all non-essential files and applications, we won't notice any degraded speeds. &lt;/p&gt;

&lt;p&gt;Outside of the Mac mini the only other hardware of note is the &lt;a href="https://www.amazon.com/gp/product/B09SCSKV1R/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&amp;amp;psc=1" rel="noopener noreferrer"&gt;KVM Switch&lt;/a&gt; I ordered so we can easily switch between the server and our family machine, which will share the same monitor, mouse, keyboard. &lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;We don't have a master plan for how we're going to run and deploy things. I know I want to learn more about running containers and container orchestration, but that's probably further down the road. The next steps we're going to focus on is getting runtimes and other utilities installed so we can run our applications. &lt;/p&gt;

&lt;p&gt;Please feel free to offer advice, encouragement, or constructive feedback in the comments.&lt;/p&gt;

</description>
      <category>operations</category>
      <category>beginners</category>
      <category>learning</category>
    </item>
    <item>
      <title>Stay Informed with PagerDuty Webhook Subscriptions</title>
      <dc:creator>Scott McAllister</dc:creator>
      <pubDate>Fri, 02 Sep 2022 23:43:41 +0000</pubDate>
      <link>https://dev.to/pdcommunity/stay-informed-with-pagerduty-webhook-subscriptions-bgd</link>
      <guid>https://dev.to/pdcommunity/stay-informed-with-pagerduty-webhook-subscriptions-bgd</guid>
      <description>&lt;p&gt;PagerDuty webhooks are evolving.&lt;/p&gt;

&lt;p&gt;With the recent introduction of v3 webhook subscriptions, v1 and v2 of PagerDuty webhooks will both be retired in the coming months (v1 EOL Oct. 2022 and v2 EOL March 2023). For those reasons alone, you'll want to update your code to use &lt;a href="https://support.pagerduty.com/docs/webhooks#manage-v3-webhook-subscriptions" rel="noopener noreferrer"&gt;v3 webhook subscriptions&lt;/a&gt; as soon as possible. But, let's also talk about what you'll be gaining by adopting v3 webhook subscriptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Webhooks at PagerDuty
&lt;/h2&gt;

&lt;p&gt;The first version of webhooks at PagerDuty covered a relatively small number of events. You essentially could only be notified when an incident was triggered, acknowledged, resolved, assigned, escalated, or delegated. &lt;/p&gt;

&lt;p&gt;An incremental update to webhooks came in the form of a v2 release which allowed you to receive webhooks whenever an incident was annotated. However, as the functionality of incidents in PagerDuty increased, so has the need to receive notifications on more than just the core incident events. Things like when an incident has been reassigned, reopened, or the status update on an incident has been published were events that needed webhooks. In addition to the new incident actions, webhooks needed to be able to support other resources in PagerDuty. Resources like Services. V3 webhooks now support when a service is created, updated, or deleted.&lt;/p&gt;

&lt;p&gt;The following table shows all the the events supported by webhooks and which version the events are supported in. The data for this table comes from the &lt;a href="https://support.pagerduty.com/docs/webhooks#version-comparison" rel="noopener noreferrer"&gt;version comparison&lt;/a&gt; section of the PagerDuty documentation on webhooks.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;v1&lt;/th&gt;
&lt;th&gt;v2&lt;/th&gt;
&lt;th&gt;v3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;incidents&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;trigger/triggered&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;acknowledge/acknowledged&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;unacknowledge/unacknowledged&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;resolve/resolved&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;assign&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;escalate/escalated&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;delegate/delegated&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;annotate/annotated&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;priority_updated&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;reassigned&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;reopened&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;responder.added&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;responder.replied&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;status_update_published&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;services&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;created&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;deleted&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;updated&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;You'll notice a few of the original incident event types have two names, such as &lt;code&gt;trigger&lt;/code&gt; and &lt;code&gt;triggered&lt;/code&gt;. That's because the names of those events changed in v3 of webhooks. If there is a &lt;code&gt;/&lt;/code&gt; in the table above the second name is the v3 version of the event name.&lt;/p&gt;

&lt;h2&gt;
  
  
  V3 Webhook Subscriptions
&lt;/h2&gt;

&lt;p&gt;V3 webhook subscriptions can be created through the &lt;a href="https://support.pagerduty.com/docs/webhooks#manage-v3-webhook-subscriptions" rel="noopener noreferrer"&gt;PagerDuty UI&lt;/a&gt; and by making calls to the API. For this article we're going to focus on managing subscriptions through &lt;a href="https://developer.pagerduty.com/docs/ZG9jOjQ1MTg4ODQ0-overview" rel="noopener noreferrer"&gt;the API&lt;/a&gt;. For information on managing subscriptions through the UI checkout the &lt;a href="https://support.pagerduty.com/docs/webhooks#manage-v3-webhook-subscriptions" rel="noopener noreferrer"&gt;Webhooks support documentation&lt;/a&gt;. &lt;/p&gt;

&lt;h3&gt;
  
  
  Create Webhook Subscription Through API
&lt;/h3&gt;

&lt;p&gt;You create a webhook subscription by making a call to the &lt;code&gt;webhook_subscriptions&lt;/code&gt; endpoint with a &lt;code&gt;webhook_subscription&lt;/code&gt; object defined in the body of the request. The subscription object has three main components--delivery method, events, and filter. &lt;/p&gt;

&lt;h4&gt;
  
  
  Delivery Method
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;delivery_method&lt;/code&gt; of a webhook subscription defines how and where a message for an event will be delivered. There are two required fields: &lt;code&gt;type&lt;/code&gt; and &lt;code&gt;url&lt;/code&gt;. At this time the only valid value for &lt;code&gt;type&lt;/code&gt; is &lt;code&gt;http_delivery_method&lt;/code&gt;, and the &lt;code&gt;url&lt;/code&gt; is the location of where the webhook should be sent.&lt;/p&gt;

&lt;h4&gt;
  
  
  Events
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;events&lt;/code&gt; field is an array of &lt;a href="https://developer.pagerduty.com/docs/ZG9jOjQ1MTg4ODQ0-overview#event-types" rel="noopener noreferrer"&gt;event types&lt;/a&gt; the webhook subscription is interested in. For a complete list of possible event times, and the syntax for including them, see the &lt;a href="https://developer.pagerduty.com/docs/ZG9jOjQ1MTg4ODQ0-overview#event-types" rel="noopener noreferrer"&gt;Event Types&lt;/a&gt; section of the &lt;a href="https://developer.pagerduty.com/docs/ZG9jOjQ1MTg4ODQ0-overview" rel="noopener noreferrer"&gt;PagerDuty Webhook developer documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Filter
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;filter&lt;/code&gt; value sets the scope of the events that are being sent. You can filter on three types--&lt;code&gt;service_reference&lt;/code&gt;,&lt;code&gt;team_reference&lt;/code&gt;, and &lt;code&gt;account_reference&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Putting all of those pieces together, creating a &lt;code&gt;webhook_subscription&lt;/code&gt; with a &lt;code&gt;service_reference&lt;/code&gt; filter would something like this:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"webhook_subscription"&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;"delivery_method"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com/receive_a_pagerduty_webhook"&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;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sends webhook events somewhere interesting."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"events"&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;"incident.acknowledged"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"incident.annotated"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"incident.delegated"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"incident.escalated"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"incident.priority_updated"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"incident.reassigned"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"incident.reopened"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"incident.resolved"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"incident.responder.added"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"incident.responder.replied"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"incident.unacknowledged"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"incident.status_update_published"&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;"filter"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PJHZQ7R"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"service_reference"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"webhook_subscription"&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;&lt;em&gt;NOTE: Something to be careful of, as of this writing, there isn't a check to see if the service ID you're passing is valid. Make sure to verify your service ID is correct before creating a &lt;code&gt;webhook_subscription&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Webhook Payload
&lt;/h3&gt;

&lt;p&gt;With the webhook subscription created it’s now ready to send webhooks on the subscribed events. The webhook payload for that event looks something like this:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"01D5RHYA931SGC5NALZ1HVKVE2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"event_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"incident.priority_updated"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"resource_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"incident"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"occurred_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2022-09-01T23:29:50.459Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"agent"&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;"html_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://pdt-stmcallister.pagerduty.com/users/P5JEPDQ"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"P5JEPDQ"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"self"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.pagerduty.com/users/P5JEPDQ"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Scott McAllister"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user_reference"&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;"client"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"data"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Q39FWCH8OEH7I0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"incident"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"self"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.pagerduty.com/incidents/Q39FWCH8OEH7I0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"html_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://pdt-stmcallister.pagerduty.com/incidents/Q39FWCH8OEH7I0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"acknowledged"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"incident_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"59124bec832148a7b9dc5e8c43e1cf7b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2022-06-10T19:27:35Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Something's Broken!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"service"&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;"html_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://pdt-stmcallister.pagerduty.com/services/P0IQNQT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"P0IQNQT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"self"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.pagerduty.com/services/P0IQNQT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Changie"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"service_reference"&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;"assignees"&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="nl"&gt;"html_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://pdt-stmcallister.pagerduty.com/users/P5JEPDQ"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"P5JEPDQ"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"self"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.pagerduty.com/users/P5JEPDQ"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Scott McAllister"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user_reference"&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="nl"&gt;"escalation_policy"&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;"html_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://pdt-stmcallister.pagerduty.com/escalation_policies/P1CSJJO"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"P1CSJJO"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"self"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.pagerduty.com/escalation_policies/P1CSJJO"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Default"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"escalation_policy_reference"&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;"teams"&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;"priority"&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;"html_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://pdt-stmcallister.pagerduty.com/account/incident_priorities"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PDNVXWA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"self"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.pagerduty.com/priorities/PDNVXWA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"P3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"priority_reference"&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;"urgency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"conference_bridge"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"resolve_reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&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;As you can see, there’s a lot to that event object. Something to notice is the event.data object. This is the meat of the incident. It’s where you’ll find information like title and status, as well as the service, assignees, and escalation policy objects. Rather than go into detail about the structure of this payload here in this post, I invite you to head over to the &lt;a href="https://developer.pagerduty.com/docs/ZG9jOjQ1MTg4ODQ0-overview#webhook-payload" rel="noopener noreferrer"&gt;Webhook Payload&lt;/a&gt; section of the &lt;a href="https://developer.pagerduty.com/docs/ZG9jOjQ1MTg4ODQ0-overview" rel="noopener noreferrer"&gt;Webhooks&lt;/a&gt; Developer documentation. &lt;/p&gt;

&lt;h3&gt;
  
  
  Let Us Know
&lt;/h3&gt;

&lt;p&gt;We'd love to hear about your experience migrating to v3 Webhook Subscriptions. Let us know in the comments how things went, or if you have any questions during your migration process.&lt;/p&gt;

</description>
      <category>pagerduty</category>
      <category>webhooks</category>
      <category>api</category>
      <category>integrations</category>
    </item>
    <item>
      <title>Requests for Go or Terraform Content?</title>
      <dc:creator>Scott McAllister</dc:creator>
      <pubDate>Mon, 28 Mar 2022 23:00:37 +0000</pubDate>
      <link>https://dev.to/stmcallister/requests-for-go-or-terraform-content-1cko</link>
      <guid>https://dev.to/stmcallister/requests-for-go-or-terraform-content-1cko</guid>
      <description>&lt;p&gt;I have found myself completely immersed in the &lt;a href="https://go.dev/" rel="noopener noreferrer"&gt;Go&lt;/a&gt; and &lt;a href="https://www.terraform.io/" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt; ecosystems lately. Most of my time is focused on learning both of these technologies. I'm learning new things all the time and would love to share them. I've written a couple posts (&lt;a href="https://dev.to/pdcommunity/write-terraform-files-in-go-with-hclwrite-2e1j"&gt;Write Terraform Files in Go with hclwrite&lt;br&gt;
&lt;/a&gt; &amp;amp; &lt;a href="https://dev.to/pdcommunity/create-terraform-resource-references-in-go-with-hclwrite-2bl6"&gt;Create Terraform Resource References in Go with hclwrite&lt;/a&gt;) on the &lt;a href="https://pkg.go.dev/github.com/hashicorp/hcl/v2/hclwrite" rel="noopener noreferrer"&gt;hclwrite&lt;/a&gt; Go library for creating HashiCorp Configuration Language (HCL) and would love to write more on Go!  &lt;/p&gt;

&lt;p&gt;Are there any posts on either Go or Terraform that you would like to see? &lt;/p&gt;

</description>
      <category>requestforpost</category>
      <category>go</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Create Terraform Resource References in Go with hclwrite</title>
      <dc:creator>Scott McAllister</dc:creator>
      <pubDate>Tue, 22 Mar 2022 06:50:26 +0000</pubDate>
      <link>https://dev.to/pdcommunity/create-terraform-resource-references-in-go-with-hclwrite-2bl6</link>
      <guid>https://dev.to/pdcommunity/create-terraform-resource-references-in-go-with-hclwrite-2bl6</guid>
      <description>&lt;p&gt;In our &lt;a href="https://dev.to/pdcommunity/write-terraform-files-in-go-with-hclwrite-2e1j"&gt;last post&lt;/a&gt; I introduced the &lt;a href="https://pkg.go.dev/github.com/hashicorp/hcl/v2/hclwrite" rel="noopener noreferrer"&gt;hclwrite&lt;/a&gt; library from HashiCorp which allows you to write &lt;a href="https://www.terraform.io/docs/language/syntax/configuration.html" rel="noopener noreferrer"&gt;HashiCorp Configuration Language (HCL)&lt;/a&gt; using &lt;a href="https://go.dev/" rel="noopener noreferrer"&gt;Go&lt;/a&gt;. In that article I created a basic HCL file that defined 150 &lt;a href="https://registry.terraform.io/providers/PagerDuty/pagerduty/latest/docs/resources/business_service" rel="noopener noreferrer"&gt;Business Service&lt;/a&gt; resources in a PagerDuty account. Truth be told, I picked Business Services because they didn't require reference values from other resources, because I didn't know how to construct those resource references using hclwrite...until now.&lt;/p&gt;

&lt;p&gt;In this post we're going to create HCL that defines a &lt;a href="https://registry.terraform.io/providers/PagerDuty/pagerduty/latest/docs/resources/service" rel="noopener noreferrer"&gt;PagerDuty Service&lt;/a&gt;. A service requires a reference to a &lt;a href="https://registry.terraform.io/providers/PagerDuty/pagerduty/latest/docs/resources/escalation_policy" rel="noopener noreferrer"&gt;PagerDuty Escalation Policy&lt;/a&gt; which, in turn, requires a reference to a &lt;a href="https://registry.terraform.io/providers/PagerDuty/pagerduty/latest/docs/resources/schedule" rel="noopener noreferrer"&gt;PagerDuty Schedule&lt;/a&gt;. Each of these references are accomplished by using a &lt;a href="https://www.terraform.io/language/expressions/references#resources" rel="noopener noreferrer"&gt;resource reference expression&lt;/a&gt;. For example, the HCL for defining a service looks something like this, where the &lt;code&gt;escalation_policy&lt;/code&gt; attribute is the ID of the escalation policy associated with the service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"pagerduty_service"&lt;/span&gt; &lt;span class="s2"&gt;"me1"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Me Service 1"&lt;/span&gt;
  &lt;span class="nx"&gt;escalation_policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pagerduty_escalation_policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We already know how to create new blocks and set primitive attribute values, as we covered those concepts in the &lt;a href="(https://dev.to/pdcommunity/write-terraform-files-in-go-with-hclwrite-2e1j)"&gt;last post&lt;/a&gt;. However, we need to first create a service resource block in order to set an &lt;code&gt;escalation_policy&lt;/code&gt; attribute. That code for creating the service resource block looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;rootBody&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AppendNewBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"resource"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"pagerduty_service"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"me1"&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
&lt;span class="n"&gt;serviceBody&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;serviceBody&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetAttributeValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cty&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StringVal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Me Service 1"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we're ready to add the &lt;code&gt;escalation_policy&lt;/code&gt; attribute to &lt;code&gt;serviceBody&lt;/code&gt;, which is where things get interesting. To create the &lt;code&gt;name&lt;/code&gt; attribute in the code above we used the &lt;code&gt;SetAttributeValue&lt;/code&gt; and the &lt;code&gt;cty.StringVal&lt;/code&gt; functions to set the string value. But, a resource reference is not a string.  It's essentially a variable. If you've been using Terraform for a while you might be thinking you could use the older expression syntax where you set the value as a string and wrapped expression with a &lt;code&gt;${ }&lt;/code&gt;. The hclwrite library actually watches for this syntax and tries to escape it by adding an extra &lt;code&gt;$&lt;/code&gt; to the front, like this &lt;code&gt;$${ }&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;There are a couple of approaches that will work for creating these expressions. The first is to use &lt;code&gt;SetAttributeTraversal&lt;/code&gt;. A &lt;a href="https://pkg.go.dev/github.com/hashicorp/hcl/v2/hclwrite#Traversal" rel="noopener noreferrer"&gt;Traversal&lt;/a&gt; is designed to define a path of an expression. To define the &lt;code&gt;escalation_policy&lt;/code&gt; as a Traversal it would look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;serviceBody&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetAttributeTraversal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"escalation_policy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hcl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Traversal&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;hcl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TraverseRoot&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"pagerduty_escalation_policy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;hcl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TraverseAttr&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"ep"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;hcl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TraverseAttr&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;    
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a perfectly formatted expression of &lt;code&gt;pagerduty_escalation_policy.ep.id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The other way to create the necessary reference syntax, and the one I prefer, is to use &lt;code&gt;SetAttributeRaw&lt;/code&gt;. This function requires you to define the entire expression manually as a series of &lt;a href=""&gt;Tokens&lt;/a&gt;. A Token is a struct containing &lt;code&gt;Type&lt;/code&gt; and &lt;code&gt;Bytes&lt;/code&gt; fields. The &lt;code&gt;Type&lt;/code&gt; comes from the &lt;code&gt;hclsyntax.TokenType&lt;/code&gt;. And &lt;code&gt;Bytes&lt;/code&gt; is a &lt;code&gt;[]byte&lt;/code&gt; with the value of the Token. In this case, where we're setting the &lt;code&gt;escalation_policy&lt;/code&gt; value, the &lt;code&gt;Tokens&lt;/code&gt; and subsequent &lt;code&gt;SetAttributeRaw&lt;/code&gt; would look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;hclwrite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tokens&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hclsyntax&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TokenIdent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`pagerduty_escalation_policy.ep.id`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;serviceBody&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetAttributeRaw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"escalation_policy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a few reasons why I prefer using the &lt;code&gt;SetAttributeRaw&lt;/code&gt; method. The first advantage you'll notice from this approach is that it takes less code than the Traversal. However, the real advantage comes when creating attributes that contain a list value. We do just that when defining a &lt;code&gt;pagerduty_schedule&lt;/code&gt; resource. In the &lt;code&gt;layer&lt;/code&gt; block of the schedule resource we set a list of &lt;code&gt;users&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The HCL for the entire PagerDuty Schedule looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"pagerduty_schedule"&lt;/span&gt; &lt;span class="s2"&gt;"foo"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Me Schedule"&lt;/span&gt;
  &lt;span class="nx"&gt;time_zone&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"America/Los_Angeles"&lt;/span&gt;

  &lt;span class="nx"&gt;layer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;                         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"This Shift"&lt;/span&gt;
    &lt;span class="nx"&gt;start&lt;/span&gt;                        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2022-03-15T20:00:00-08:00"&lt;/span&gt;
    &lt;span class="nx"&gt;rotation_virtual_start&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2022-03-15T20:00:00-08:00"&lt;/span&gt;
    &lt;span class="nx"&gt;rotation_turn_length_seconds&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt;
    &lt;span class="nx"&gt;users&lt;/span&gt;                        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pagerduty_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;me&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="nx"&gt;restriction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"daily_restriction"&lt;/span&gt;
      &lt;span class="nx"&gt;start_time_of_day&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"17:00:00"&lt;/span&gt;
      &lt;span class="nx"&gt;duration_seconds&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;54000&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Go code needed to create this &lt;code&gt;pagerduty_schedule&lt;/code&gt; resource and &lt;code&gt;layer&lt;/code&gt; blocks looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// create schedule&lt;/span&gt;
&lt;span class="n"&gt;sched&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;rootBody&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AppendNewBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"resource"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"pagerduty_schedule"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;schedBody&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;sched&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;schedBody&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetAttributeValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cty&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StringVal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Me Schedule"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;schedBody&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetAttributeValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"time_zone"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cty&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StringVal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"America/Los_Angeles"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;schedBody&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AppendNewline&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;schedLayer&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;schedBody&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AppendNewBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"layer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;schedLayerBody&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;schedLayer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;schedLayerBody&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetAttributeValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cty&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StringVal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"This Shift"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;schedLayerBody&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetAttributeValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cty&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StringVal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2022-03-15T20:00:00-08:00"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;schedLayerBody&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetAttributeValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rotation_virtual_start"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cty&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StringVal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2022-03-15T20:00:00-08:00"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creating the Tokens for the &lt;code&gt;users&lt;/code&gt; list looks extremely similar to the code we used above to create the &lt;code&gt;escalation_policy&lt;/code&gt; reference. The difference now is that we wrap our &lt;code&gt;data.pagerduty_user.me.id&lt;/code&gt; value in opening and closing square brackets, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;userTokens&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;hclwrite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tokens&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;hclsyntax&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TokenOBrack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`[`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;hclsyntax&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TokenIdent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`data.pagerduty_user.me.id`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;hclsyntax&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TokenCBrack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`]`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;schedLayerBody&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetAttributeRaw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userTokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hopefully, you now have a better idea of how to use the &lt;code&gt;SetAttributeRaw&lt;/code&gt; function when writing  resource references in HCL when using the &lt;code&gt;hclwrite&lt;/code&gt; library. You can find all the code that was described in this article in the &lt;a href="https://github.com/stmcallister/create-terraform-files-go" rel="noopener noreferrer"&gt;Create Terraform Files in Go&lt;/a&gt; repository on GitHub.&lt;/p&gt;

</description>
      <category>go</category>
      <category>terraform</category>
      <category>devops</category>
      <category>programming</category>
    </item>
    <item>
      <title>Write Terraform Files in Go with hclwrite</title>
      <dc:creator>Scott McAllister</dc:creator>
      <pubDate>Wed, 18 Aug 2021 07:49:37 +0000</pubDate>
      <link>https://dev.to/pdcommunity/write-terraform-files-in-go-with-hclwrite-2e1j</link>
      <guid>https://dev.to/pdcommunity/write-terraform-files-in-go-with-hclwrite-2e1j</guid>
      <description>&lt;p&gt;I am one of the lead maintainers of the &lt;a href="https://registry.terraform.io/providers/PagerDuty/pagerduty/latest/docs" rel="noopener noreferrer"&gt;PagerDuty Terraform Provider&lt;/a&gt; which means I find myself writing a lot of &lt;a href="https://www.terraform.io/" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt; definitions in &lt;a href="https://www.terraform.io/docs/language/syntax/configuration.html" rel="noopener noreferrer"&gt;HashiCorp Configuration Language (HCL)&lt;/a&gt;. The provider itself contains a healthy collection of acceptance tests, but I often still write some of my own HCL to verify use cases and make sure bug fixes address specific issues raised by users.&lt;/p&gt;

&lt;p&gt;To create these Terraform definitions I commonly write the HCL by hand. However, this can get tedious if I need to create, say, 150 of the same resource to test &lt;a href="https://developer.pagerduty.com/docs/rest-api-v2/pagination/" rel="noopener noreferrer"&gt;pagination&lt;/a&gt; coming from the &lt;a href="https://developer.pagerduty.com/api-reference/" rel="noopener noreferrer"&gt;PagerDuty API&lt;/a&gt;. Historically, I used a Python script that verbosely wrote out the HCL syntax like so&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;createService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;tf_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;resource &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;pagerduty_service&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt; {&lt;/span&gt;&lt;span class="se"&gt;\n\
&lt;/span&gt;&lt;span class="s"&gt;    name = &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"\n\
&lt;/span&gt;&lt;span class="s"&gt;    escalation_policy = pagerduty_escalation_policy.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.id &lt;/span&gt;&lt;span class="se"&gt;\n\
&lt;/span&gt;&lt;span class="s"&gt;    alert_creation = &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;create_alerts_and_incidents&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="se"&gt;\n\
&lt;/span&gt;&lt;span class="s"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tf_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Functions like this &lt;code&gt;createService&lt;/code&gt; above would be written for each resource and put inside of a loop to generate the HCL resource blocks that were needed for the definition. Honestly, this method worked fine for me. At least it did until I wondered if Go provided a better way. That's when I discovered the &lt;a href="https://pkg.go.dev/github.com/hashicorp/hcl/v2/hclwrite" rel="noopener noreferrer"&gt;hclwrite&lt;/a&gt; package from HashiCorp.   &lt;/p&gt;

&lt;p&gt;The project documentation describes hclwrite as a package that, "deals with the problem of generating HCL configuration and of making specific surgical changes to existing HCL configurations." This turned out to be exactly the package I needed. Rather than writing out HCL syntax by hand the hclwrite package would do that for me while I just called functions to create the objects. &lt;/p&gt;

&lt;p&gt;This article will help you get started with the hclwrite package by walking you through how I used it to generate a Terraform configuration for creating 150 Business Services in PagerDuty. I'll step through some of the concepts I struggled with and wished there were more examples of.&lt;/p&gt;

&lt;p&gt;The hclwrite package is imported from &lt;code&gt;github.com/hashicorp/hcl/hclwrite&lt;/code&gt;. This tripped me up a bit, partly because I'm still relatively new to Go and partly because the path to the package listed in the documentation is slightly different. For the rest of you also new to Go, your imports should look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/hashicorp/hcl/v2/hclwrite"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/zclconf/go-cty/cty"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll notice another import for the &lt;a href="https://pkg.go.dev/github.com/zclconf/go-cty/cty" rel="noopener noreferrer"&gt;go-cty&lt;/a&gt; package. This package (pronounced see-tie) provides some infrastructure for a type system that might be useful for applications that need to represent configuration values. You'll see it used throughout the examples when setting types to attribute values.&lt;/p&gt;

&lt;p&gt;To get things started you'll need to create two different types of file objects. One for hclwrite and another for the filesystem. This can be done with the following code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// create new empty hcl file object&lt;/span&gt;
&lt;span class="n"&gt;hclFile&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;hclwrite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewEmptyFile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c"&gt;// create new file on system&lt;/span&gt;
&lt;span class="n"&gt;tfFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"bservelist.tf"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;// initialize the body of the new file object&lt;/span&gt;
&lt;span class="n"&gt;rootBody&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;hclFile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The content of every object in hclwrite is stored in a body object. To add or append anything to an object you'll need to reference its body. In the code above you see that we named the body of the HCL document object &lt;code&gt;rootBody&lt;/code&gt;. The first thing we need to do with &lt;code&gt;rootBody&lt;/code&gt; is set up the &lt;code&gt;provider&lt;/code&gt; block for the &lt;a href="https://registry.terraform.io/providers/PagerDuty/pagerduty/latest/docs" rel="noopener noreferrer"&gt;PagerDuty provider&lt;/a&gt;. The HCL for this block looks like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"pagerduty"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"yeahRightN0tgo1ng2t3llyOuTh@t"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Constructing this block using the &lt;code&gt;hclwrite&lt;/code&gt; package requires the &lt;a href="https://pkg.go.dev/github.com/hashicorp/hcl/v2/hclwrite#Body.AppendNewBlock" rel="noopener noreferrer"&gt;AppendNewBlock&lt;/a&gt; function which is expecting two arguments. First is a string which will set the type of block and the second argument is an array of strings that act as labels for the block. In the case of this &lt;code&gt;provider&lt;/code&gt; block we want to set the label simply as &lt;code&gt;pagerduty&lt;/code&gt;. That would look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;rootBody&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AppendNewBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"pagerduty"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside this block we need to set a &lt;code&gt;token&lt;/code&gt; attribute to the value of a PagerDuty API Key. Remember, the attribute needs to be added to the body of the block. So, we'll first set that body value to the &lt;code&gt;providerBody&lt;/code&gt; variable and then call &lt;a href="https://pkg.go.dev/github.com/hashicorp/hcl/v2/hclwrite#Body.SetAttributeValue" rel="noopener noreferrer"&gt;SetAttributeValue&lt;/a&gt;, passing the label and value of the attribute as arguments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;providerBody&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;providerBody&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetAttributeValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;cty&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StringVal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PAGERDUTY_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next block we are going to create is the &lt;code&gt;terraform&lt;/code&gt; block where we define which providers we're going to use and the versions of those providers. What's interesting about these definitions is they require two nested blocks that don't have any labels. The &lt;code&gt;AppendNewBlock&lt;/code&gt; function handles his by accepting a &lt;code&gt;nil&lt;/code&gt; argument for the labels. Creating the two blocks looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;tfBlock&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;rootBody&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AppendNewBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"terraform"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tfBlockBody&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;tfBlock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;reqProvsBlock&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;tfBlockBody&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AppendNewBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"required_providers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;reqProvsBlockBody&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;reqProvsBlock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the &lt;code&gt;required_providers&lt;/code&gt; block we need to define an attribute called &lt;code&gt;pagerduty&lt;/code&gt; that contains a value of an object with two key-value pairs as fields. This is done by setting the value to a &lt;code&gt;cty.ObjectVal&lt;/code&gt; as a map.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;reqProvsBlockBody&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetAttributeValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pagerduty"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;cty&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ObjectVal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;cty&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"source"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;cty&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StringVal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PagerDuty/pagerduty"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s"&gt;"version"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cty&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StringVal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1.10.1"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generated HCL for this code will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;pagerduty&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"PagerDuty/pagerduty"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2.3.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it's time to start creating actual resources. Remember, our task was to create 150 Business Services. In HCL, a resource block is just like the other block types we've created. The main difference is resource blocks contain multiple labels. In this case, each resource block contains the &lt;code&gt;pagerduty_business_service&lt;/code&gt; label for the resource type  along with the identifier label for the resource. Because we only care about creating a whole lot of Business Services it doesn't matter what they're named. So, we're just going to use the index &lt;code&gt;i&lt;/code&gt; from our loop to put a number variable into each Business Service name. For example, the name of the first Business Service will be "Business Service 1". The code for creating those resource blocks looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;bs&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;rootBody&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AppendNewBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"resource"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"pagerduty_business_service"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"bs%v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
&lt;span class="n"&gt;bsBody&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;bsBody&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetAttributeValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;cty&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StringVal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Business Service %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="n"&gt;rootBody&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AppendNewline&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the looping is done, and the HCL for all 150 Business Service definitions has been generated, the last thing to do is to write all the definitions out to a &lt;code&gt;.tf&lt;/code&gt; file. There are a few ways to do this. I went for the way I was most familiar with, where I wrote the bytes from the &lt;code&gt;hclwrite&lt;/code&gt; file object to the &lt;code&gt;tfFile&lt;/code&gt;, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;tfFile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it. You should be able to run the configurations in the &lt;code&gt;.tf&lt;/code&gt; file you created and populated. To see all the code used in these examples checkout the &lt;a href="https://github.com/stmcallister/create-terraform-files-go" rel="noopener noreferrer"&gt;Create Terraform Files in Go&lt;/a&gt; project over on GitHub. This was also only an introduction to the &lt;code&gt;hclwrite&lt;/code&gt; package. Go see the &lt;a href="https://pkg.go.dev/github.com/hashicorp/hcl/v2@v2.10.1/hclwrite" rel="noopener noreferrer"&gt;hclwrite documentation&lt;/a&gt; to see all the available functions.&lt;/p&gt;

</description>
      <category>go</category>
      <category>terraform</category>
      <category>devops</category>
    </item>
    <item>
      <title>Assigning a Google Domain to GitLab Pages</title>
      <dc:creator>Scott McAllister</dc:creator>
      <pubDate>Sat, 03 Aug 2019 18:33:19 +0000</pubDate>
      <link>https://dev.to/stmcallister/assigning-a-google-domain-to-gitlab-pages-22ap</link>
      <guid>https://dev.to/stmcallister/assigning-a-google-domain-to-gitlab-pages-22ap</guid>
      <description>&lt;p&gt;Before I jump into the nitty gritty of how to add a domain registered with &lt;a href="https://domains.google.com" rel="noopener noreferrer"&gt;Google Domains&lt;/a&gt; to a &lt;a href="https://about.gitlab.com/product/pages/" rel="noopener noreferrer"&gt;GitLab Pages&lt;/a&gt; site, I want to share a little backstory. If you're not interested in reading the human side of how and why I learned about custom domains with GitLab Pages, please skip ahead to the GitLab Pages section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backstory
&lt;/h2&gt;

&lt;p&gt;A few years ago, I arrived home to my eight-year-old son excitedly telling me that he found a book at the library about HTML and built a website. This happened as a complete surprise and without any prodding from his software developer dad. After seeing the site, the dialog went something like this.&lt;/p&gt;

&lt;p&gt;I told him, "that's great! Want me to put it on a server for you?"&lt;/p&gt;

&lt;p&gt;"What's that!?" he asked.&lt;/p&gt;

&lt;p&gt;"On a server, anyone can come see your site," I explained.&lt;/p&gt;

&lt;p&gt;"Yeah!" &lt;/p&gt;

&lt;p&gt;At the time, I was using a web host that required me to upload files via FTP. This proved to be a bottleneck in my son's workflow. Every time he had an update, he would bug me to upload it to the server.&lt;/p&gt;

&lt;p&gt;Finally, I got wise and got out of his way. I put his code in a &lt;a href="https://gitlab.com" rel="noopener noreferrer"&gt;GitLab&lt;/a&gt; repository, and set up a &lt;a href="https://about.gitlab.com/product/pages/" rel="noopener noreferrer"&gt;GitLab Pages&lt;/a&gt; site. Then, I showed him the command line--which he tought was exceedingly cool--and taught him a few simple Git commands. And, voila! My son could publish changes as often as he wanted, without waiting for his old man.&lt;/p&gt;

&lt;p&gt;This process continues to work well for both of us. But recently, I wanted him to feel a little more ownership of his site. You see, all of the sites he built were being served off of my generic GitLab domain &lt;a href="https://stmcallister.gitlab.io/webkid/" rel="noopener noreferrer"&gt;https://stmcallister.gitlab.io/webkid/&lt;/a&gt;. So, rather than looking like he was piggybacking on my site, I wanted to get my son his own domain. &lt;/p&gt;

&lt;p&gt;We went over to Google Domains and searched long and hard to find the name he wanted and arrived at &lt;a href="http://ajmcweb.com" rel="noopener noreferrer"&gt;http://ajmcweb.com&lt;/a&gt;. It's short. It's easy to spell. And, most importantly, he liked it! &lt;/p&gt;

&lt;p&gt;Now, I needed to figure out how to assign the domain to the site. That's where the instruction piece of this article begins.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitLab Pages
&lt;/h2&gt;

&lt;p&gt;In the GitLab repository for the Pages site, go to Settings -&amp;gt; Pages. On the Settings page click the green New Domain button in the upper right corner. &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%2Fwrouohl2qzuw53jqfu3q.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%2Fwrouohl2qzuw53jqfu3q.png" alt="GitLab Pages New Domain Button" width="800" height="229"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the New Pages Domain page enter the domain you want to use for your GitLab pages site. There are also options for entering certificates and private keys. Since the site we're simply static and not working with any kind of information, we did not set either of these values. When ready, click Create New Domain.&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%2Fujwxcs60zw8e6jj85ord.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%2Fujwxcs60zw8e6jj85ord.png" alt="GitLab Pages New Domain Form" width="800" height="763"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the Pages Domain page you'll see a form entry for DNS that shows the CNAME entry of your new domain pointing to your GitLab Pages domain. Just below the DNS field is Verification status. Copy the value for Verification status, you'll need to add this to your Google Domain account to verify ownership of the domain you want to use. &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%2Flmxxmh9iezjs5bqnaw9c.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%2Flmxxmh9iezjs5bqnaw9c.png" alt="GitLab Pages Domain Settings" width="800" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Google Domains
&lt;/h2&gt;

&lt;p&gt;In Google Domains, select your domain, go to the DNS section, and scroll down to Custom resource records. You will add two resource records to point your Google domain to your GitLab pages site.&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%2Ffeb0i0b1npl9k44w3e12.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%2Ffeb0i0b1npl9k44w3e12.png" alt="Google Domains DNS Custom Resources" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, take the value copied from the GitLab Verification status and create a resource record of type TXT. Paste the GitLab verification code in the Text field. The default value for TTL is set to 1H, but if you want to see changes occur more quickly, change it to 1m. Then, click Add.&lt;/p&gt;

&lt;p&gt;TTL stands for Time to Live, and it stands for how long a copy of the record stays in cache before being discarded. That means if you leave the TTL set to 1m, then that resource is getting looked up every minute. So, after changing the value to 1m to see the DNS change populate, it would be a good idea to change it back to the 1H, or even 1D, value to reduce the numbers of look ups.&lt;/p&gt;

&lt;p&gt;The next resource record to add is a DNS A record, which gives your domain an IP address for GitLab pages. As of this writing, that value is &lt;code&gt;35.185.44.232&lt;/code&gt;. You may also want to check the &lt;a href="https://docs.gitlab.com/ee/user/project/pages/getting_started_part_three.html#dns-a-record" rel="noopener noreferrer"&gt;DNS A record&lt;/a&gt; documentation on GitLab Pages to make sure this value is still current.&lt;/p&gt;

&lt;p&gt;With those two records set in Google Domains, go back to the Pages Domain page on GitLab and click the reload circle arrow to check if the domain now verifies. The unverified message should turn green and change to Verified. And, for a final test, point your browser to your new domain, and you should see your GitLab Pages site.&lt;/p&gt;

&lt;p&gt;Now, my son is happy that his site has a custom domain. And--hopefully--you're happy that you found all this information in one article. You're welcome. ;)&lt;/p&gt;

</description>
      <category>gitlab</category>
      <category>dns</category>
      <category>devops</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Create a Quick Local Web Server with Python and Ngrok</title>
      <dc:creator>Scott McAllister</dc:creator>
      <pubDate>Sun, 12 May 2019 04:50:06 +0000</pubDate>
      <link>https://dev.to/stmcallister/create-a-quick-local-web-server-with-python-and-ngrok-k0</link>
      <guid>https://dev.to/stmcallister/create-a-quick-local-web-server-with-python-and-ngrok-k0</guid>
      <description>&lt;p&gt;One of the nice things about starting a new job is that you have to setup all of your development tools and environments from scratch. And some of the things you thought you'd never forget stubbornly hide behind the memories that are much easier to recall.&lt;/p&gt;

&lt;p&gt;Such was the case for me recently. On a machine that was only a few weeks old, I needed to make an HTML file quickly accessible to the outside world. I had done this dozens of times before, but for the life of me, I could not remember the exact tools I needed to get this job done. &lt;/p&gt;

&lt;p&gt;Then, after much searching, the fog cleared. All I needed was to run a single Python command to fire up a local web server. Then, using a tool called &lt;a href="https://ngrok.com/" rel="noopener noreferrer"&gt;ngrok&lt;/a&gt; I could make that server accessible to the world.&lt;/p&gt;

&lt;p&gt;This blog post is more of an insurance policy against my faulty memory, so I can easily remind myself of these things in the future. However, you are more than welcome to continue reading if this sounds interesting to you. First, let's get the Python web server running on port 8000.&lt;/p&gt;

&lt;h3&gt;
  
  
  Python
&lt;/h3&gt;

&lt;p&gt;A super quick way to fire up a web server, with virtually no configuration (especially if you're on macOS) is to use Python's SimpleHttpServer. In Python 2.7 (which is the default version that comes with macOS) the server is started by running &lt;code&gt;python -m SimpleHTTPServer 8000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In Python 3 this can be done by running &lt;code&gt;python -m http.server 8000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With this command, the &lt;code&gt;SimpleHTTPServer&lt;/code&gt; will serve up the contents of the current directory on port 8000. Any port can be specified here, but make sure it's one that is not currently in use. The contents can be accessed locally by pointing your browser to &lt;a href="http://localhost:8000/" rel="noopener noreferrer"&gt;http://localhost:8000/&lt;/a&gt;. Now that the simple web server is running, it's time to configure ngrok to make this server accessible by the Internet at large.&lt;/p&gt;

&lt;h3&gt;
  
  
  ngrok
&lt;/h3&gt;

&lt;p&gt;Ngrok is a tool that provides a secure tunnel for a local application to access the Internet, and for the Internet to access it. This tunneling comes in handy when demoing or testing web sites without having to deploy them. For instance, if you're building an application that subscribes to webhooks, and those webhooks need a callback URL to access the application on your machine, ngrok would provide that address for the webhooks to call. In my case, I just needed a simple way to serve up HTML files.&lt;/p&gt;

&lt;p&gt;There are a lot of features to ngrok, but this article is only going to focus on providing a simple http tunnel on a specified port. This can be done with the following steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Download ngrok from the &lt;a href="https://ngrok.com/download" rel="noopener noreferrer"&gt;ngrok Download Page&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Extract the ngrok executable and put it somewhere that makes sense to you. I put it in &lt;code&gt;/Applications/Utilities&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;To get the &lt;code&gt;ngrok&lt;/code&gt; executable into &lt;code&gt;PATH&lt;/code&gt; so that it's executable from anywhere on your system, create a symlink in &lt;code&gt;/usr/local/bin/&lt;/code&gt; that points to where ever &lt;code&gt;ngrok&lt;/code&gt; is saved. If it's in the Utilities directory, as mentioned above, the symlink command would look like this &lt;code&gt;ln -s /Application/Utilities/ngrok /usr/local/bin/ngrok&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Go the the directory where &lt;code&gt;SimpleHTTPServer&lt;/code&gt; is running and run the following command
&lt;code&gt;ngrok http 8000&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The ngrok output then shows the following:&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%2Fma11f7fufjmg9bps6dhe.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%2Fma11f7fufjmg9bps6dhe.png" alt="Screenshot of ngrok" width="800" height="319"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The important piece you're looking for are the values for &lt;code&gt;Forwarding&lt;/code&gt;. The local web server will now be visible at the &lt;code&gt;x.ngrok.io&lt;/code&gt; domains. As requests are made to this tunnel, the results are logged in the Connections section of the ngrok CLI. For the free version of ngrok, this session will last for 8 hours. When the ngrok process expires, or is restarted, the subdomain value in the address will change. If you're looking for a consistent address, I believe you can purchase a license from ngrok.&lt;/p&gt;

&lt;p&gt;Now with these two simple tools you can spin up publicly accessible web servers to your heart's content. &lt;/p&gt;

</description>
      <category>ngrok</category>
      <category>webdev</category>
      <category>python</category>
    </item>
  </channel>
</rss>
