<?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: Shi Han</title>
    <description>The latest articles on DEV Community by Shi Han (@shihanng).</description>
    <link>https://dev.to/shihanng</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%2F154703%2F3aae4de4-3af9-45ed-8e11-c9d31c1e0bc4.jpg</url>
      <title>DEV Community: Shi Han</title>
      <link>https://dev.to/shihanng</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shihanng"/>
    <language>en</language>
    <item>
      <title>Managing S3 bucket for Terraform backend in the same configuration</title>
      <dc:creator>Shi Han</dc:creator>
      <pubDate>Mon, 31 Jan 2022 11:12:16 +0000</pubDate>
      <link>https://dev.to/shihanng/managing-s3-bucket-for-terraform-backend-in-the-same-configuration-2c6c</link>
      <guid>https://dev.to/shihanng/managing-s3-bucket-for-terraform-backend-in-the-same-configuration-2c6c</guid>
      <description>&lt;h2&gt;
  
  
  What is Terraform backend?
&lt;/h2&gt;

&lt;p&gt;A Terraform backend is a place where Terraform uses to store its state. Terraform supports various types of backend. The default &lt;a href="https://www.terraform.io/language/settings/backends/local" rel="noopener noreferrer"&gt;local backend&lt;/a&gt; stores the state files in the local filesystem. Local backend is useful for creating resources for testing purposes in a "quick and dirty" manner. However, it is common practice to use backends that store states in remote services such as &lt;a href="https://www.terraform.io/language/settings/backends/s3" rel="noopener noreferrer"&gt;Amazon S3 (S3)&lt;/a&gt; when working on production resources as it supports state locking and versioning. Managing S3 bucket for Terraform backend in the same configuration&lt;/p&gt;

&lt;h2&gt;
  
  
  The chicken-and-egg problem
&lt;/h2&gt;

&lt;p&gt;The following is an example of using an S3 bucket as Terraform backend. Notice that we need to specify the name of the S3 &lt;code&gt;bucket&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 1.1.4"&lt;/span&gt;
  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"s3"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-s3-backend-pmh86b2v"&lt;/span&gt;
    &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ap-northeast-1"&lt;/span&gt;
    &lt;span class="nx"&gt;key&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform.tfstate"&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 above means that we need to have the S3 bucket for the backend before writing Terraform configurations to manage other resources. What if we also want to create the S3 bucket for the backend using Terraform? A straightforward approach is to manage the S3 bucket for the backend in a separate Terraform configuration that uses a local backend. We want to show how to manage that in the same configuration as other resources in this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution
&lt;/h2&gt;

&lt;p&gt;Let’s take a look at the following Terraform configuration. We are going to create two &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket" rel="noopener noreferrer"&gt;S3 buckets&lt;/a&gt;: one for the backend (&lt;code&gt;terraform-s3-backend-pmh86b2v&lt;/code&gt;) and another one is the actual bucket that we need for our project (&lt;code&gt;my-project-...&lt;/code&gt;) (If you plan to follow this “tutorial,” please change the bucket name as they need to be globally unique). Notice that at this point, we are using a local backend.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 1.1.4"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ap-northeast-1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"backend"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-s3-backend-pmh86b2v"&lt;/span&gt;
  &lt;span class="nx"&gt;acl&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"private"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket_prefix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-project-"&lt;/span&gt;
  &lt;span class="nx"&gt;acl&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"private"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We start by &lt;strong&gt;only&lt;/strong&gt; creating the S3 bucket (&lt;code&gt;terraform-s3-backend-pmh86b2v&lt;/code&gt;) for the backend using the &lt;a href="https://learn.hashicorp.com/tutorials/terraform/resource-targeting?in=terraform/cli" rel="noopener noreferrer"&gt;target flag &lt;code&gt;-target&lt;/code&gt;&lt;/a&gt;. We can see that the command above also creates a state file (&lt;code&gt;terraform.tfstate&lt;/code&gt;) in our local directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ terraform plan -target=aws_s3_bucket.backend -out=/tmp/tfplan
$ terraform apply /tmp/tfplan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we configure Terraform to use our new S3 bucket as its backend by changing the backend block.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/main.tf b/main.tf
index c6d846b..19547ae 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/main.tf
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/main.tf
&lt;/span&gt;&lt;span class="p"&gt;@@ -1,5 +1,10 @@&lt;/span&gt;
 terraform {
   required_version = "~&amp;gt; 1.1.4"
&lt;span class="gi"&gt;+  backend "s3" {
+    bucket = "terraform-s3-backend-pmh86b2v"
+    region = "ap-northeast-1"
+    key    = "remote_terraform.tfstate"
+  }
&lt;/span&gt; }
&lt;span class="err"&gt;
&lt;/span&gt; provider "aws" {
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we run &lt;code&gt;terraform plan&lt;/code&gt;, Terraform will detect the changes in the backend and prompt us to reinitialize. Run recommended the command &lt;code&gt;terraform init&lt;/code&gt; to make the switch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ terraform plan -out=/tmp/tfplan
╷
│ Error: Backend initialization required, please run "terraform init"
│
│ Reason: Initial configuration of the requested backend "s3"
...
$ terraform init

Initializing the backend...
Do you want to copy existing state to the new backend?
  Pre-existing state was found while migrating the previous "local" backend to the
  newly configured "s3" backend. No existing state was found in the newly
  configured "s3" backend. Do you want to copy this state to the new "s3"
  backend? Enter "yes" to copy and "no" to start with an empty state.

  Enter a value: yes
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The migration copies the state file to S3. We can confirm this via the AWS CLI or from AWS Management Console.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;aws s3 &lt;span class="nb"&gt;ls &lt;/span&gt;terraform-s3-backend-pmh86b2v
&lt;span class="go"&gt;2022-01-31 19:46:06       1734 remote_terraform.tfstate
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.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%2Fjqcsu8vtpkw2pvsk2exw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fjqcsu8vtpkw2pvsk2exw.png" alt="Confirming that Terraform state file is stored in S3 via AWS Management Console."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the migration is complete, we can use Terraform (&lt;code&gt;plan&lt;/code&gt;, &lt;code&gt;apply&lt;/code&gt;, etc.) as usual.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to migrate from the S3 backend to the local backend?
&lt;/h2&gt;

&lt;p&gt;As Terraform will detect the changes in the backend, change the backend block back to local to migrate the state file to a local directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;diff --git a/main.tf b/main.tf
index 19547ae..e5cac0b 100644
--- a/main.tf
+++ b/main.tf
@@ -1,10 +1,6 @@
 terraform {
   required_version = "~&amp;gt; 1.1.4"
-  backend "s3" {
-    bucket = "terraform-s3-backend-pmh86b2v"
-    region = "ap-northeast-1"
-    key    = "remote_terraform.tfstate"
-  }
+  backend "local" {}
 }

 provider "aws" {
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When running &lt;code&gt;terraform plan&lt;/code&gt;, Terraform will notice the change in backend and prompt us to run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;terraform init &lt;span class="nt"&gt;-migrate-state&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since Terraform will not remove the state files from the previous backend, we have to remove the contents in the S3 bucket before removing the bucket from the Terraform configuration. Or, we can configure the backend bucket with &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket#argument-reference" rel="noopener noreferrer"&gt;&lt;code&gt;force_destroy = true&lt;/code&gt;&lt;/a&gt; before removing it from the Terraform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final remarks
&lt;/h2&gt;

&lt;p&gt;As shown above, Terraform can automatically detect changes in the backend configuration. We can use this feature to help solve the chicken-and-egg problem when using the S3 bucket as Terraform backend. Please find the sample code in this article at &lt;a href="https://github.com/shihanng/terraform-s3-backend" rel="noopener noreferrer"&gt;https://github.com/shihanng/terraform-s3-backend&lt;/a&gt;.&lt;/p&gt;




&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://unsplash.com/photos/M5tzZtFCOfs" rel="noopener noreferrer"&gt;Cover image&lt;/a&gt; by &lt;a href="https://unsplash.com/@tvick" rel="noopener noreferrer"&gt;Taylor Vick&lt;/a&gt;, licensed under &lt;a href="https://unsplash.com/license" rel="noopener noreferrer"&gt;Unsplash License&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>terraform</category>
      <category>aws</category>
      <category>devops</category>
    </item>
    <item>
      <title>Hosting personal website on DigitalOcean's App Platform</title>
      <dc:creator>Shi Han</dc:creator>
      <pubDate>Sun, 10 Jan 2021 06:28:52 +0000</pubDate>
      <link>https://dev.to/shihanng/hosting-personal-website-on-digitalocean-s-app-platform-1cep</link>
      <guid>https://dev.to/shihanng/hosting-personal-website-on-digitalocean-s-app-platform-1cep</guid>
      <description>&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;A simple portfolio website.&lt;/p&gt;

&lt;h3&gt;
  
  
  Category Submission:
&lt;/h3&gt;

&lt;p&gt;Personal Site/Portfolio&lt;/p&gt;

&lt;h3&gt;
  
  
  App Link
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://ng.shihan.dev/"&gt;https://ng.shihan.dev/&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Screenshots
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4F6ibwQd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/shihanng/articles/trunk/ng-shihan-dev/./images/screenshot.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4F6ibwQd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/shihanng/articles/trunk/ng-shihan-dev/./images/screenshot.png" alt="Screenshot of https://ng.shihan.dev"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bfW32Gnj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/shihanng/articles/trunk/ng-shihan-dev/./images/screenshot-responsive.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bfW32Gnj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/shihanng/articles/trunk/ng-shihan-dev/./images/screenshot-responsive.png" alt="Screenshot (mobile) of https://ng.shihan.dev"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Description
&lt;/h3&gt;

&lt;p&gt;It is a simple and clean portfolio with &lt;a href="https://www.gatsbyjs.com/"&gt;Gatsby&lt;/a&gt;, &lt;a href="https://www.typescriptlang.org/"&gt;TypeScript&lt;/a&gt;, &lt;a href="https://reactjs.org/"&gt;React&lt;/a&gt;, and &lt;a href="https://tailwindcss.com/"&gt;tailwindcss&lt;/a&gt;. I come from a backend/infrastructure background, so frontend development is a challenge for me. I created this site without using any existing theme also made it responsive and looks good on mobile. It's not a super fanny site, but it's mine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Link to Source Code
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/shihanng/ng.shihan.dev/"&gt;https://github.com/shihanng/ng.shihan.dev/&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Permissive License
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/shihanng/ng.shihan.dev/blob/trunk/LICENSE"&gt;MIT&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;I had a bit of free time at the end of 2020, so I've decided to rebuild my portfolio website from &lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt; to Gatsby. My motivation for rebuilding is to get more practice in TypeScript / React, the skills I picked up in 2020. I also want to try out tailwindcss too. Coincidently, I found out about &lt;a href="https://dev.to/devteam/announcing-the-digitalocean-app-platform-hackathon-on-dev-2i1k"&gt;DigitalOcean App Platform Hackathon on DEV&lt;/a&gt;. So, I decided to host the new site on the platform instead of GitHub Pages.&lt;/p&gt;

&lt;h3&gt;
  
  
  How I built it (A detailed guide of deploying a Gatsby site to DigitalOcean’s App Platform)
&lt;/h3&gt;

&lt;p&gt;I bootstrapped the site with &lt;code&gt;gatsby new&lt;/code&gt; command. We don't need to tell Gatsby that we are using DigitalOcean's App Platform.&lt;/p&gt;

&lt;p&gt;To create a new App, select &lt;strong&gt;App&lt;/strong&gt; from the Create menu in the DigitalOcean console.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zoDiFgJJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/shihanng/articles/trunk/ng-shihan-dev/./images/menu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zoDiFgJJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/shihanng/articles/trunk/ng-shihan-dev/./images/menu.png" alt="New App"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To deploy to DigitalOcean's App Platform from GitHub, we need to connect to our repository from the &lt;a href="https://cloud.digitalocean.com/apps/"&gt;App console&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---CKxo1-6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/shihanng/articles/trunk/ng-shihan-dev/./images/link-gh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---CKxo1-6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/shihanng/articles/trunk/ng-shihan-dev/./images/link-gh.png" alt="Link GitHub account"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This means that we need to install DigitalOcean on GitHub. It can also be installed in an organization account.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qfmwY_xr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/shihanng/articles/trunk/ng-shihan-dev/./images/install-do-gh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qfmwY_xr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/shihanng/articles/trunk/ng-shihan-dev/./images/install-do-gh.png" alt="Install DigitalOcean"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can also limit the repository that DigitalOcean has access to. E.g. for my case, I am only allowing it to access the repository that contains the source code of my site.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iGHmqVCO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/shihanng/articles/trunk/ng-shihan-dev/./images/limit-repo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iGHmqVCO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/shihanng/articles/trunk/ng-shihan-dev/./images/limit-repo.png" alt="Limit repository access"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once we have the permission setup on GitHub, we can then select a repository from DigitalOcean's App Platform.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ymvuvkjh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/shihanng/articles/trunk/ng-shihan-dev/./images/select-repo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ymvuvkjh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/shihanng/articles/trunk/ng-shihan-dev/./images/select-repo.png" alt="Select repository on DO"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, name our app, select a region, and pick the deployment branch. DigitalOcean will pick up any changes that I push to the trunk branch and deploy it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Rsq7JYGa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/shihanng/articles/trunk/ng-shihan-dev/./images/name-region-branch.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Rsq7JYGa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/shihanng/articles/trunk/ng-shihan-dev/./images/name-region-branch.png" alt="Set name, region, and branch"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see that DigitalOcean will be able to detect that the repository is a Gatsby project automatically. I did not need to change any settings here for my usecase.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AHQ7t3p0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/shihanng/articles/trunk/ng-shihan-dev/./images/gatsby.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AHQ7t3p0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/shihanng/articles/trunk/ng-shihan-dev/./images/gatsby.png" alt="Gatsby detected"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, we need to select a pricing plan. We can use the &lt;strong&gt;Starter&lt;/strong&gt; plan for static sites like this one. We can build and deploy up to three Starter apps for free.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tLtX4Xy4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/shihanng/articles/trunk/ng-shihan-dev/./images/plan.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tLtX4Xy4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/shihanng/articles/trunk/ng-shihan-dev/./images/plan.png" alt="Starter plan"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After this, we can relax and grab a cup of coffee while waiting for DigitalOcean to build and deploy the site.&lt;/p&gt;

</description>
      <category>dohackathon</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Publishing DEV articles from GitHub</title>
      <dc:creator>Shi Han</dc:creator>
      <pubDate>Sun, 13 Sep 2020 06:02:57 +0000</pubDate>
      <link>https://dev.to/shihanng/publishing-dev-articles-from-github-3jc2</link>
      <guid>https://dev.to/shihanng/publishing-dev-articles-from-github-3jc2</guid>
      <description>&lt;p&gt;How do you write your article for &lt;a href="https://dev.to/"&gt;DEV&lt;/a&gt;? Do you use the markdown editor provided by DEV on the browser? Or do you use your favorite editor on your local machine? If you do use a regular editor on your PC, how do you submit the article to DEV?&lt;/p&gt;

&lt;p&gt;I like to use &lt;a href="https://neovim.io/"&gt;my favorite editor&lt;/a&gt; to write articles. When I am ready to publish an article, I have to copy and paste the texts into the online editor, then click the "Publish" button. Sometimes I want to see how DEV is showing the article while I am in the middle of the writing. Therefore, my writing experience usually involves more than one copy-pasting. As you can imagine, this is troublesome, tedious, and not a good experience at all.&lt;/p&gt;

&lt;p&gt;I also like to use &lt;a href="https://git-scm.com/"&gt;Git&lt;/a&gt; to manage the versions of my articles. Then I can also push the articles into &lt;a href="https://github.com"&gt;GitHub&lt;/a&gt;. Since GitHub renders Markdown files, readers also can read the articles on GitHub if they choose to. Of course, images should work as expected on GitHub if we use relative links and commit the image files too. Now I also have a backup system for my articles.&lt;/p&gt;

&lt;h1&gt;
  
  
  Workflow with GitHub Actions
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--05Hpo7FA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/shihanng/articles/trunk/devto/./images/dev_submission_flow.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--05Hpo7FA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/shihanng/articles/trunk/devto/./images/dev_submission_flow.svg" alt="Flow of submitting article to DEV from GitHub"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I want everything described above to be fully automated. The diagram above shows the workflow I want to realize with GitHub Actions. When I want to start writing a new article, I will branch out from the main branch and start writing. When I want to see how this article looks on DEV, I can push the new branch to GitHub and make a (draft) pull request. At this instance, my GitHub Action should submit the article to DEV as a draft. I can merge the branch back into the main branch when I am ready to publish the article.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;code&gt;devto&lt;/code&gt;: the CLI Tool
&lt;/h1&gt;

&lt;p&gt;The first thing that I need to make all these happen is a CLI tool that helps me submit articles to DEV from the terminal. For this, I've created a CLI tool called &lt;code&gt;devto&lt;/code&gt; with Go.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vJ70wriM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/shihanng"&gt;
        shihanng
      &lt;/a&gt; / &lt;a href="https://github.com/shihanng/devto"&gt;
        devto
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      CLI tool to publish article to https://dev.to/
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
devto -- publish to &lt;a href="https://dev.to" rel="nofollow"&gt;dev.to&lt;/a&gt; from your terminal&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://github.com/shihanng/devto/actions?query=workflow%3Amain"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XSPaBawL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/shihanng/devto/workflows/main/badge.svg%3Fbranch%3Ddevelop" alt="CI"&gt;&lt;/a&gt;
&lt;a href="https://github.com/shihanng/devto/actions?query=workflow%3Arelease"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nr3bC0mI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/shihanng/devto/workflows/release/badge.svg" alt="Release"&gt;&lt;/a&gt;
&lt;a href="https://github.com/shihanng/devto/releases"&gt;&lt;img src="https://camo.githubusercontent.com/6bf706197647953dfd6dfc428e3962fe2d97a79b/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f73686968616e6e672f646576746f" alt="GitHub release (latest by date)"&gt;&lt;/a&gt;
&lt;a href="https://coveralls.io/github/shihanng/devto?branch=develop" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/95bd46cec1294eca0e7946d99f007c6731811d39/68747470733a2f2f636f766572616c6c732e696f2f7265706f732f6769746875622f73686968616e6e672f646576746f2f62616467652e7376673f6272616e63683d646576656c6f70" alt="Coverage Status"&gt;&lt;/a&gt;
&lt;a href="https://goreportcard.com/report/github.com/shihanng/devto" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/7559fd5437f6f28983ed059fa4e9224460667ae2/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f73686968616e6e672f646576746f" alt="Go Report Card"&gt;&lt;/a&gt;
&lt;a href="https://raw.githubusercontent.com/shihanng/devto/develop/./LICENSE"&gt;&lt;img src="https://camo.githubusercontent.com/c9bc72e47460302b35acf8ce840c4faeea1e19f8/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f73686968616e6e672f646576746f" alt="GitHub"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
What is this?&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;devto&lt;/code&gt; is a CLI tool that helps submit articles to DEV from the terminal. It makes use of the &lt;a href="https://docs.dev.to/api/" rel="nofollow"&gt;APIs that DEV kindly provides in OpenAPI specification&lt;/a&gt;. &lt;code&gt;devto&lt;/code&gt; mainly does two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;It collects all image links from the Markdown file into a &lt;code&gt;devto.yml&lt;/code&gt; file with the &lt;code&gt;generate&lt;/code&gt; subcommand. For example, if we have &lt;code&gt;./image-1.png&lt;/code&gt; and &lt;code&gt;./image-2.png&lt;/code&gt; in the Markdown file, we will get the following:&lt;/p&gt;
&lt;div class="highlight highlight-source-yaml"&gt;&lt;pre&gt;&lt;span class="pl-ent"&gt;images&lt;/span&gt;
  &lt;span class="pl-ent"&gt;./image-1.png&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
  &lt;span class="pl-ent"&gt;./image-2.png&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It submits the article to DEV with the &lt;code&gt;submit&lt;/code&gt; subcommand. The &lt;code&gt;submit&lt;/code&gt; subcommand creates a new article in DEV and updates the &lt;code&gt;devto.yml&lt;/code&gt; with the resulting &lt;code&gt;article_id&lt;/code&gt;. &lt;code&gt;devto&lt;/code&gt; will use this &lt;code&gt;article_id&lt;/code&gt; in the following execution to perform an update operation instead of creating a new entry for the same article.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The DEV API does not have a way of uploading…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/shihanng/devto"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The tool makes use of the &lt;a href="https://docs.dev.to/api/"&gt;APIs that DEV kindly provides in OpenAPI specification&lt;/a&gt;. &lt;code&gt;devto&lt;/code&gt; mainly does two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It collects all image links from the Markdown file into a &lt;code&gt;devto.yml&lt;/code&gt; file with the &lt;code&gt;generate&lt;/code&gt; subcommand. For example, if we have &lt;code&gt;./image-1.png&lt;/code&gt; and &lt;code&gt;./image-2.png&lt;/code&gt; in the Markdown file, we will get the following:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;images&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;./image-1.png&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
    &lt;span class="s"&gt;./image-2.png&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;It submits the article to DEV with the &lt;code&gt;submit&lt;/code&gt; subcommand. The &lt;code&gt;submit&lt;/code&gt; subcommand creates a new article in DEV and updates the &lt;code&gt;devto.yml&lt;/code&gt; with the resulting &lt;code&gt;article_id&lt;/code&gt;. &lt;code&gt;devto&lt;/code&gt; will use this &lt;code&gt;article_id&lt;/code&gt; in the following execution to perform an update operation instead of creating a new entry for the same article.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The DEV API does not have a way of uploading images yet. If we submit a Markdown content with relative paths of image links, DEV will not be able to show those images. As a workaround of this problem, we need to provide a full path for the images either manually via the &lt;code&gt;devto.yml&lt;/code&gt; file or using the &lt;code&gt;--prefix&lt;/code&gt; flag. We will elaborate more on this in the GitHub Actions section.&lt;/p&gt;
&lt;h1&gt;
  
  
  &lt;code&gt;devto-act&lt;/code&gt;: the GitHub Action
&lt;/h1&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vJ70wriM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/shihanng"&gt;
        shihanng
      &lt;/a&gt; / &lt;a href="https://github.com/shihanng/devto-act"&gt;
        devto-act
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
devto's GitHub Action&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://github.com/shihanng/devto-act/actions?query=workflow%3ATests"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DUoszjWM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/shihanng/devto-act/workflows/Tests/badge.svg" alt="Tests"&gt;&lt;/a&gt;
&lt;a href="https://github.com/shihanng/devto-act/blob/trunk/LICENSE"&gt;&lt;img src="https://camo.githubusercontent.com/a913442339e51e881118fe11fe9bf8c67841eace/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f73686968616e6e672f646576746f2d616374" alt="GitHub"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;devto-act&lt;/code&gt; is a GitHub Action that helps you to submit your article to &lt;a href="https://dev.to/" rel="nofollow"&gt;DEV&lt;/a&gt; from GitHub. It uses &lt;a href="https://github.com/shihanng/devto"&gt;devto&lt;/a&gt; internally.&lt;/p&gt;
&lt;h2&gt;
Inputs&lt;/h2&gt;
&lt;h3&gt;
&lt;code&gt;devto_api_key&lt;/code&gt;
&lt;/h3&gt;
&lt;p&gt;We need this to submit the article. You can get it from &lt;a href="https://dev.to/settings/account" rel="nofollow"&gt;https://dev.to/settings/account&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
&lt;code&gt;skip_generate&lt;/code&gt;
&lt;/h3&gt;
&lt;p&gt;Skip generation of links.&lt;/p&gt;
&lt;h3&gt;
&lt;code&gt;published&lt;/code&gt;
&lt;/h3&gt;
&lt;p&gt;Make the article public if non empty.&lt;/p&gt;
&lt;h3&gt;
&lt;code&gt;dry_run&lt;/code&gt;
&lt;/h3&gt;
&lt;p&gt;Dry run if not empty. Will not send to &lt;a href="https://dev.to/" rel="nofollow"&gt;DEV&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
&lt;code&gt;auto_prefix&lt;/code&gt;
&lt;/h3&gt;
&lt;p&gt;Generate prefix (cover) image links based on the branch and path to markdown files. Will be applied to the &lt;code&gt;devto submit&lt;/code&gt; as the value of &lt;code&gt;--prefix&lt;/code&gt; flag.&lt;/p&gt;
&lt;h3&gt;
&lt;code&gt;markdown_files&lt;/code&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Required&lt;/strong&gt; Path to markdown files (space separated) to be submitted. Can be relative to the root of the repository.&lt;/p&gt;
&lt;h2&gt;
Example Usage&lt;/h2&gt;
&lt;div class="highlight highlight-source-yaml"&gt;&lt;pre&gt;- &lt;span class="pl-ent"&gt;name&lt;/span&gt;: &lt;span class="pl-s"&gt;Publish to DEV&lt;/span&gt;
  &lt;span class="pl-ent"&gt;uses&lt;/span&gt;: &lt;span class="pl-s"&gt;shihanng/devto-act@v0.0.6&lt;/span&gt;
  &lt;span class="pl-ent"&gt;with&lt;/span&gt;:
    &lt;span class="pl-ent"&gt;auto_prefix&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;yes&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
    &lt;span class="pl-ent"&gt;markdown_files&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;filename.md&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
  &lt;span class="pl-ent"&gt;env&lt;/span&gt;:
    &lt;span class="pl-ent"&gt;DEVTO_API_KEY&lt;/span&gt;: &lt;span class="pl-s"&gt;${{ secrets.DEVTO_API_KEY }}&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;

  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/shihanng/devto-act"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;This GitHub Action mainly uses the &lt;code&gt;devto&lt;/code&gt; CLI tool with one "special" feature: the auto prefix functionality. If you have the following GitHub repository &lt;code&gt;user/repo&lt;/code&gt; and you are pushing a new branch &lt;code&gt;new-article&lt;/code&gt; for a new article, &lt;code&gt;devto-act&lt;/code&gt; will use &lt;code&gt;https://raw.githubusercontent.com/user/repo/new-article/&lt;/code&gt; as the value of the &lt;code&gt;--prefix&lt;/code&gt; flag for the &lt;code&gt;submit&lt;/code&gt; subcommand. As a result, image links in the Markdown content, e.g.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;![&lt;/span&gt;&lt;span class="nv"&gt;Image One&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;./images/one.jpg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;![&lt;/span&gt;&lt;span class="nv"&gt;Image Two&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;./images/two.jpg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;will become&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;![&lt;/span&gt;&lt;span class="nv"&gt;Image One&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://raw.githubusercontent.com/user/repo/new-article/./images/one.jpg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;![&lt;/span&gt;&lt;span class="nv"&gt;Image Two&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://raw.githubusercontent.com/user/repo/new-article/./images/two.jpg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;on DEV. That means we are hosting the images on GitHub instead of DEV.&lt;/p&gt;

&lt;h1&gt;
  
  
  My Workflow
&lt;/h1&gt;

&lt;p&gt;Using the CLI tool and the GitHub Action above, I put together a workflow that suits my needs in &lt;a href="https://github.com/shihanng/articles"&gt;shihanng/articles&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DEV&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trunk"&lt;/span&gt;

  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;submit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&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;Check out code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&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;Find Markdown files&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;technote-space/get-diff-action@v3&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;diff&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;SUFFIX_FILTER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.md&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;Trim output from diff&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;trim&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;files="$(echo -n "${{ steps.diff.outputs.diff }}" | tr -d "'")"&lt;/span&gt;
          &lt;span class="s"&gt;echo "::set-output name=trimmed::$files"&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;Submit to DEV as draft&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shihanng/devto-act@v0.0.6&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event_name == 'pull_request' }}&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;auto_prefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yes"&lt;/span&gt;
          &lt;span class="na"&gt;markdown_files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.trim.outputs.trimmed }}&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;DEVTO_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DEVTO_API_KEY }}&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;Publish to DEV&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shihanng/devto-act@v0.0.6&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event_name == 'push' }}&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;auto_prefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yes"&lt;/span&gt;
          &lt;span class="na"&gt;markdown_files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.trim.outputs.trimmed }}&lt;/span&gt;
          &lt;span class="na"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yes"&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;DEVTO_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DEVTO_API_KEY }}&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;Update devto.yml&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;EndBug/add-and-commit@v4&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event_name == 'pull_request' }}&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;author_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AUTHOR_NAME }}&lt;/span&gt;
          &lt;span class="na"&gt;author_email&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AUTHOR_EMAIL }}&lt;/span&gt;
          &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Update&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;devto.yml"&lt;/span&gt;
          &lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;**/devto.yml"&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The workflow contains the following steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;First, we need to know which Markdown files that we need to publish because we don't want to republish everything on every pull request. We do this with the help of &lt;a href="https://github.com/technote-space/get-diff-action"&gt;technote-space/get-diff-action&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Next, we have two independent steps that help us submit articles to DEV with &lt;code&gt;devto-act&lt;/code&gt;. One submits the article as a draft if the GitHub event is a &lt;code&gt;pull request&lt;/code&gt;. The other one publishes the article if the event is a &lt;code&gt;push&lt;/code&gt; (which we limit to the main branch).&lt;/li&gt;
&lt;li&gt;Finally, we commit the changes in the &lt;code&gt;devto.yml&lt;/code&gt; file to the same branch because we want to keep the &lt;code&gt;article_id&lt;/code&gt; for future updates. This step is made possible with &lt;a href="https://github.com/EndBug/add-and-commit"&gt;EndBug/add-and-commit&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Submission Category:
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Wacky Wildcards&lt;/code&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Ending
&lt;/h1&gt;

&lt;p&gt;I've been using &lt;code&gt;devto&lt;/code&gt; and &lt;code&gt;devto-act&lt;/code&gt; to publish my last few articles on DEV. Although it is still not perfect, I think it is time to share it with you. If you are like me, want to write DEV articles in your favorite and manage it on GitHub, please give these two tools a try:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://github.com/shihanng/devto"&gt;&lt;code&gt;devto&lt;/code&gt;&lt;/a&gt;: If you want to submit the article from your terminal&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/shihanng/devto-act"&gt;&lt;code&gt;devto-act&lt;/code&gt;&lt;/a&gt;: If you want to automate the publishing workflow&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Feedbacks in any form (issues, PRs, comments below) are welcome. Please give these repositories a ⭐ if you find them useful. Those are always my main source of motivation!&lt;/p&gt;

&lt;p&gt;Thank you for reading ❤️.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>github</category>
      <category>actionshackathon</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How to read CSV file from Amazon S3 in Python</title>
      <dc:creator>Shi Han</dc:creator>
      <pubDate>Sat, 22 Aug 2020 13:38:53 +0000</pubDate>
      <link>https://dev.to/shihanng/how-to-read-csv-file-from-amazon-s3-in-python-4ee9</link>
      <guid>https://dev.to/shihanng/how-to-read-csv-file-from-amazon-s3-in-python-4ee9</guid>
      <description>&lt;p&gt;Here is a scenario. There is a huge CSV file on Amazon S3. We need to write a Python function that downloads, reads, and prints the value in a specific column on the standard output (stdout).&lt;/p&gt;

&lt;p&gt;Simple Googling will lead us to the answer to this assignment in Stack Overflow. The code should look like something like the following:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;codecs&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;


&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;s3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_csv_from_s3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bucket_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DictReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;codecs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getreader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;We will explore the solution above in detail in this article. Imagine this like a rubber duck programming and you are the rubber duck in this case.&lt;/p&gt;
&lt;h2&gt;
  
  
  Downloading File from S3
&lt;/h2&gt;

&lt;p&gt;Let's get started. First, we need to figure out how to download a file from S3 in Python. The official AWS SDK for Python is known as Boto3. &lt;a href="https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#client" rel="noopener noreferrer"&gt;According to the documentation&lt;/a&gt;, we can create the &lt;code&gt;client&lt;/code&gt; instance for S3 by calling &lt;code&gt;boto3.client("s3")&lt;/code&gt;. Then we call the &lt;a href="https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.get_object" rel="noopener noreferrer"&gt;&lt;code&gt;get_object()&lt;/code&gt;&lt;/a&gt; method on the &lt;code&gt;client&lt;/code&gt; with bucket name and key as input arguments to download a specific file.&lt;/p&gt;

&lt;p&gt;Now the thing that we are interested in is the return value of the &lt;a href="https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.get_object" rel="noopener noreferrer"&gt;&lt;code&gt;get_object()&lt;/code&gt;&lt;/a&gt; method call. The return value is a Python dictionary. In the &lt;code&gt;Body&lt;/code&gt; key of the dictionary, we can find the content of the file downloaded from S3. The body &lt;code&gt;data["Body"]&lt;/code&gt; is a &lt;a href="https://botocore.amazonaws.com/v1/documentation/api/latest/reference/response.html#botocore-response" rel="noopener noreferrer"&gt;&lt;code&gt;botocore.response.StreamingBody&lt;/code&gt;&lt;/a&gt;. Hold that thought.&lt;/p&gt;
&lt;h2&gt;
  
  
  Reading CSV File
&lt;/h2&gt;

&lt;p&gt;Let's switch our focus to handling CSV files. We want to access the value of a specific column one by one. &lt;a href="https://docs.python.org/3/library/csv.html#csv.DictReader" rel="noopener noreferrer"&gt;&lt;code&gt;csv.DictReader&lt;/code&gt;&lt;/a&gt; from the standard library seems to be an excellent candidate for this job. It returns an &lt;a href="https://docs.python.org/3/library/stdtypes.html#iterator-types" rel="noopener noreferrer"&gt;iterator&lt;/a&gt; (the class implements the iterator methods &lt;code&gt;__iter__()&lt;/code&gt; and &lt;code&gt;__next__()&lt;/code&gt;) that we can use to access each row in a for-loop: &lt;code&gt;row[column]&lt;/code&gt;. But what should we pass into X as an argument? According to the documentation, we should refer to the reader instance.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;All other optional or keyword arguments are passed to the underlying reader instance.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There we can see that the first argument &lt;code&gt;csvfile&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;can be any object which supports the iterator protocol and returns a string each time its &lt;strong&gt;next&lt;/strong&gt;() method is called&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;botocore.response.StreamingBody&lt;/code&gt; supports the &lt;a href="https://docs.python.org/3/glossary.html#term-iterator" rel="noopener noreferrer"&gt;iterator protocol&lt;/a&gt; 🎉.&lt;/p&gt;


&lt;div class="ltag__replit"&gt;
  &lt;iframe height="550px" src="https://repl.it/@shihanng/streamingbody?lite=true"&gt;&lt;/iframe&gt;
&lt;/div&gt;



&lt;p&gt;Unfortunately, it's &lt;code&gt;__next__()&lt;/code&gt; method does not return a string but bytes instead.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;

&lt;p&gt;_csv.Error: iterator should return strings, not bytes (did you open the file in text mode?)&lt;/p&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Reading CSV file from S3&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;So how do we bridge the gap between &lt;code&gt;botocore.response.StreamingBody&lt;/code&gt; type and the type required by the &lt;code&gt;cvs&lt;/code&gt; module? We want to "convert" the bytes to string in this case. Therefore, the &lt;a href="https://docs.python.org/3/library/codecs.html#module-codecs" rel="noopener noreferrer"&gt;codecs module of Python's standard library&lt;/a&gt; seems to be a place to start.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Most standard codecs are text encodings, which encode text to bytes&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Since we are doing the opposite, we are looking for a "decoder," specifically a decoder that can handle stream data: &lt;a href="https://docs.python.org/3/library/codecs.html#streamreader-objects" rel="noopener noreferrer"&gt;&lt;code&gt;codecs.StreamReader&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Decodes data from the stream and returns the resulting object.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;code&gt;codecs.StreamReader&lt;/code&gt; takes a &lt;a href="https://docs.python.org/3/glossary.html#term-file-object" rel="noopener noreferrer"&gt;file-like object&lt;/a&gt; as an input argument. In Python, this means the object should have a &lt;code&gt;read()&lt;/code&gt; method. The &lt;code&gt;botocore.response.StreamingBody&lt;/code&gt; does have the &lt;code&gt;read()&lt;/code&gt; method: &lt;a href="https://botocore.amazonaws.com/v1/documentation/api/latest/reference/response.html#botocore.response.StreamingBody.read" rel="noopener noreferrer"&gt;https://botocore.amazonaws.com/v1/documentation/api/latest/reference/response.html#botocore.response.StreamingBody.read&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since the &lt;code&gt;codecs.StreamReader&lt;/code&gt; also supports the iterator protocol, we can pass the object of this instance into the &lt;code&gt;csv.DictReader&lt;/code&gt;: &lt;a href="https://github.com/python/cpython/blob/1370d9dd9fbd71e9d3c250c8e6644e0ee6534fca/Lib/codecs.py#L642-L651" rel="noopener noreferrer"&gt;https://github.com/python/cpython/blob/1370d9dd9fbd71e9d3c250c8e6644e0ee6534fca/Lib/codecs.py#L642-L651&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The final piece of the puzzle is: How do we create the &lt;code&gt;codecs.StreamReader&lt;/code&gt;? That's where the &lt;a href="https://docs.python.org/3/library/codecs.html#codecs.getreader" rel="noopener noreferrer"&gt;&lt;code&gt;codecs.getreader()&lt;/code&gt;&lt;/a&gt; function comes in play. We pass the codec of our choice (in this case, &lt;code&gt;utf-8&lt;/code&gt;) into the &lt;code&gt;codecs.getreader()&lt;/code&gt;, which creates the&lt;code&gt;codecs.StreamReader&lt;/code&gt;. This allows us to read the CSV file row-by-row into dictionary by passing the &lt;code&gt;codec.StreamReader&lt;/code&gt; into &lt;code&gt;csv.DictReader&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fshihanng%2Farticles%2Ftrunk%2Fs3-python-csv%2F.%2Fimages%2Fstreaming.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fshihanng%2Farticles%2Ftrunk%2Fs3-python-csv%2F.%2Fimages%2Fstreaming.png" alt="Reading botocore.response.StreamingBody through csv.DictReader."&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Thank you for following this long and detailed (maybe too exhausting) explanation of such a short program. I hope you find it useful. Thank your listening ❤️.&lt;/p&gt;

</description>
      <category>python</category>
      <category>codenewbie</category>
      <category>beginners</category>
      <category>aws</category>
    </item>
    <item>
      <title>Memory Reservation in Amazon Elastic Container Service</title>
      <dc:creator>Shi Han</dc:creator>
      <pubDate>Wed, 12 Aug 2020 01:27:17 +0000</pubDate>
      <link>https://dev.to/shihanng/memory-reservation-in-amazon-elastic-container-service-3l4h</link>
      <guid>https://dev.to/shihanng/memory-reservation-in-amazon-elastic-container-service-3l4h</guid>
      <description>&lt;p&gt;Amazon Elastic Container Service (ECS) provides the means to developers to orchestrate Docker containers running in Amazon Web Services (AWS). Using ECS develops will be able to deploy and command a fleet of Docker containers, scale the services, etc. in a very flexible manner within the ECS cluster.&lt;/p&gt;

&lt;p&gt;We use &lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definitions.html" rel="noopener noreferrer"&gt;&lt;strong&gt;task definition&lt;/strong&gt;&lt;/a&gt; to describe how we want a Docker container to be deployed in an ECS cluster. &lt;code&gt;memoryReservation&lt;/code&gt; is one of the &lt;strong&gt;container definitions&lt;/strong&gt; that need to be specified when writing the task definition, see &lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#container_definitions" rel="noopener noreferrer"&gt;Task definition parameters by AWS&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If a task-level memory value is not specified, you must specify a non-zero integer for one or both of memory or memoryReservation in a container definition.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Specifying X amount of &lt;code&gt;memoryReservation&lt;/code&gt; tells ECS that this particular Docker container might need X amount of memory in MiB. It is a soft limit, which means that the container is allowed to use more than we reserved. Also &lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#container_definitions" rel="noopener noreferrer"&gt;from the same reference&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When system memory is under contention, Docker attempts to keep the container memory to this soft limit; however, your container can consume more memory when needed, up to either the hard limit specified with the memory parameter (if applicable), or all of the available memory on the container instance, whichever comes first.&lt;/p&gt;

&lt;p&gt;The Docker daemon reserves a minimum of 4 MiB of memory for a container, so you should not specify fewer than 4 MiB of memory for your containers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Reading the above, some might get the idea that why not be flexible and just set &lt;code&gt;memoryReservation&lt;/code&gt; for all containers to 4 MiB then let ECS figures out how many memory that the container needs. In this article, we want to help the readers to understand why this is not a good idea. Then also try to help the readers to choose a reasonable value for &lt;code&gt;memoryReservation&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Experimental Setup
&lt;/h1&gt;

&lt;p&gt;For demonstration purposes, we are going to setup a very simple playground in ECS. It is an EC2 launch type ECS cluster with a single ECS instance of type &lt;a href="https://aws.amazon.com/ec2/instance-types/" rel="noopener noreferrer"&gt;&lt;strong&gt;t2.micro&lt;/strong&gt; that has 1 GiB of memory&lt;/a&gt;. In the cluster, we will create an ECS service that consists of following task definition:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"worker"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"alexeiled/stress-ng:latest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"--vm-bytes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"300m"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"--vm-keep"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"--vm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"-t"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"1d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"-l"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"0"&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;"memoryReservation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;400&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;Essentially, this task definition will launch a container that is constantly consuming &lt;code&gt;300 MB / 1.049 = 286.102 MiB&lt;/code&gt; of memory (see &lt;a href="https://manpages.ubuntu.com/manpages/artful/man1/stress-ng.1.html" rel="noopener noreferrer"&gt;&lt;code&gt;stress-ng&lt;/code&gt;&lt;/a&gt;). We can think of this as a memory hungry worker. The Terraform configuration of the set up is available in GitHub:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/shihanng" rel="noopener noreferrer"&gt;
        shihanng
      &lt;/a&gt; / &lt;a href="https://github.com/shihanng/ecs-resource-exp" rel="noopener noreferrer"&gt;
        ecs-resource-exp
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;ecs-resource-exp&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/shihanng/ecs-resource-exp/blob/trunk/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/a078e760507de692dbf995a67459d44c1f3d84eaf1b996922af91ba86fbcac8e/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f73686968616e6e672f6563732d7265736f757263652d657870" alt="GitHub"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Terraform configuration to setup simple Amazon Elastic Container Service (ECS) cluster for demonstrating memory usage.&lt;/p&gt;

&lt;div class="highlight highlight-text-shell-session notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c1"&gt;cd tf&lt;/span&gt;
&lt;span class="pl-c1"&gt;terraform plan -out tfplan&lt;/span&gt;
&lt;span class="pl-c1"&gt;terraform apply tfplan&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;/div&gt;

  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/shihanng/ecs-resource-exp" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;h1&gt;
  
  
  The Experiments
&lt;/h1&gt;

&lt;p&gt;The following shows the metrics that we collected during our tests. We tested two different ECS configurations. The first half shows what happened when the memory is properly reserved. The second half was where a very small value was used as &lt;code&gt;memoryReservation&lt;/code&gt;. We will walkthrough the whole process from &lt;code&gt;(A)&lt;/code&gt; to &lt;code&gt;(F)&lt;/code&gt; in the following text.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fshihanng%2Farticles%2Ftrunk%2Fecs-memory%2F.%2Fimages%2Fresults.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fshihanng%2Farticles%2Ftrunk%2Fecs-memory%2F.%2Fimages%2Fresults.png" alt="Metrics collected during tests."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  First part: The right way
&lt;/h2&gt;

&lt;p&gt;Since we know that our container will consume around 286 MiB of memory, the &lt;code&gt;memoryReservation&lt;/code&gt; is set to 400 MiB to begin the test.&lt;br&gt;
Let's start the test by placing one task into the ECS cluster. We accomplish that by setting the &lt;strong&gt;desired count&lt;/strong&gt; to &lt;code&gt;1&lt;/code&gt;.&lt;br&gt;
From our CloudWatch metrics (see &lt;code&gt;(A)&lt;/code&gt;), we can see that after placing one task into the cluster, we essentially reserving 41% and using 30% of the memory from the cluster in average. Service memory utilization is at 75.25% means that we are using 75.25% or the memory that we reserved for the service. The formulas for how these values are derived is available in the &lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/cloudwatch-metrics.html#cluster_reservation" rel="noopener noreferrer"&gt;Amazon ECS CloudWatch metrics&lt;/a&gt; documentation.&lt;/p&gt;

&lt;p&gt;The metrics also show that increasing the number of desired count from 1 to 2 (see &lt;code&gt;(B)&lt;/code&gt;) worked as expected: Both memory reservation and utilization average percentage doubled. Service memory utilization stays at 75.25% because we are still using 75.25% or the memory that we reserved for the service even though the usage of the memory in MiB is doubled.&lt;/p&gt;

&lt;p&gt;Finally we want to try to "break" the system by increasing the desired task to 3 in &lt;code&gt;(C)&lt;/code&gt;. Adding the third task means that we need to reserve &lt;code&gt;3 x 400 MiB = 1200 MiB&lt;/code&gt; for our workers.&lt;/p&gt;

&lt;p&gt;Since this is more than what the cluster could support, ECS will tell us the following:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;service ecs-memory-exp-default-worker was unable to place a task because no container instance met all of its requirements. The closest matching container-instance xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx has insufficient memory available. For more information, see the Troubleshooting section.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;instead of forcefully placing the third task. Note that we observed no fluctuation in our CloudWatch metrics in &lt;code&gt;(C)&lt;/code&gt;.&lt;br&gt;
This is a good fail-safe. Failing to place the third task does not cause any disruption to the running tasks. What we need to do now is to add more resources into the cluster for the additional tasks that we want to run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Second part: What could go wrong?
&lt;/h2&gt;

&lt;p&gt;Now, let's repeat the same experiment with one different setting: only 100 MiB of &lt;code&gt;memoryReservation&lt;/code&gt;. Here, we set the desired count to 1 at &lt;code&gt;(D)&lt;/code&gt; and then to 2 at &lt;code&gt;(E)&lt;/code&gt;.&lt;br&gt;
We can observe in the metrics that cluster memory utilization is always higher than what we've reserved. Service memory utilization is around 301% that means we are using three times more than what we promised to use.&lt;/p&gt;

&lt;p&gt;Let's try to put the third task into the cluster by setting the desired task to 3. Since ECS thought that the task only need 100 MiB, it will try to run the third task. However, this will cause one of the three tasks to stop due to insufficient memory, then ECS will again rerun the task to fulfill the desired count condition. As we can see from the events log and task statuses, and the fluctuation in the metrics &lt;code&gt;(F)&lt;/code&gt;, there is an endless loop of running and stopping of ECS task. The fluctuation is a sign of an unstable system.&lt;/p&gt;

&lt;p&gt;Note that although it may seem that we still have enough memory (there was still 39% leftover) for another task, we also need to consider the memory that is required by the host system (see &lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/memory-management.html#ecs-reserved-memory" rel="noopener noreferrer"&gt;Container Instance Memory Management&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fshihanng%2Farticles%2Ftrunk%2Fecs-memory%2F.%2Fimages%2Fecs-console.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fshihanng%2Farticles%2Ftrunk%2Fecs-memory%2F.%2Fimages%2Fecs-console.png" alt="ECS console showing endless loop of respawning tasks."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We also need to consider the problem where we have no direct control of which task/container to kill when there is memory insufficient issue (see &lt;a href="https://aws.amazon.com/blogs/containers/how-amazon-ecs-manages-cpu-and-memory-resources/" rel="noopener noreferrer"&gt;How Amazon ECS manages CPU and memory resources&lt;/a&gt; by AWS):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If containers try to consume memory between these two values (or between the soft limit and the host capacity if a hard limit is not set), they may compete with each other. In this case, what happens depends on the heuristics used by the Linux kernel’s OOM (Out of Memory) killer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What if the Linux kernel's OOM killer decides to terminate a worker that is currently processing huge data rather than the newly spun-up worker? In a radical case, this means that it is possible that we could not process anything even though it may seem that we still have two workers running, they might be killed before completed their works.&lt;/p&gt;

&lt;h1&gt;
  
  
  Wrap-up
&lt;/h1&gt;

&lt;p&gt;Yes, ECS allows us to set a very small value for &lt;code&gt;memoryReservation&lt;/code&gt;, but that does not mean it is a good idea to do it. We might find a “clever” workaround in such a small and simple system as one demonstrated above. However, production systems are usually more complex with multiple task definitions and container instances. Therefore, it is more difficult to spot the memory issue and to find the root cause of the endless restart in the real world. We can avoid all of these issues by using proper values set as &lt;code&gt;memoryReservation&lt;/code&gt;. &lt;a href="https://docs.docker.com/config/containers/resource_constraints/#understand-the-risks-of-running-out-of-memory" rel="noopener noreferrer"&gt;Docker also recommends&lt;/a&gt; that we correctly limit the memory usage of a container to mitigate the risk of system instability caused by Out Of Memory Exception (OOME).&lt;/p&gt;

&lt;p&gt;So if not a minimal value, how should we set the &lt;code&gt;memoryReservation&lt;/code&gt;? &lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#container_definitions" rel="noopener noreferrer"&gt;AWS recommends that&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;... if your container normally uses 128 MiB of memory, but occasionally bursts to 256 MiB of memory for short periods of time, you can set a memoryReservation of 128 MiB, and a memory hard limit of 300 MiB. This configuration would allow the container to only reserve 128 MiB of memory from the remaining resources on the container instance, but also allow the container to consume more memory resources when needed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the real world, it is not always that straight forward to know how much memory our application needs in contrast to the example we have above. Service memory utilization can be a metric to help us figure out how much memory our container normally uses. When its average is close to or exceeds 100%, it shows that our application is using more than what we expected, and we should increase the reservation or the resource accordingly.&lt;/p&gt;

&lt;p&gt;Thanks for reading. I hope that you find this article useful ❤️.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>docker</category>
      <category>learning</category>
      <category>beginners</category>
    </item>
    <item>
      <title>tfvar - A tool to help you write Terraform's variable definitions</title>
      <dc:creator>Shi Han</dc:creator>
      <pubDate>Tue, 18 Feb 2020 12:16:18 +0000</pubDate>
      <link>https://dev.to/shihanng/tfvar-a-tool-to-help-you-write-terraform-s-variable-definitions-1j65</link>
      <guid>https://dev.to/shihanng/tfvar-a-tool-to-help-you-write-terraform-s-variable-definitions-1j65</guid>
      <description>&lt;p&gt;Whenever I want to start working on an existing &lt;a href="https://www.terraform.io/"&gt;Terraform&lt;/a&gt; project or using a Terraform &lt;a href="https://www.terraform.io/docs/configuration/modules.html"&gt;module&lt;/a&gt;, the first thing that I find myself doing often is looking for the input variables.  If the project is small enough, the input variables are usually being declared in the &lt;code&gt;main.tf&lt;/code&gt; file.  For a well organized Terraform configurations we can find a &lt;code&gt;variables.tf&lt;/code&gt; file.  However, the locations or where to put the input variable declarations is just a convention and is not enforced syntactically.  It is our job as the user of the configurations/modules to track down all variables and make sure that they have the values &lt;a href="https://www.terraform.io/docs/configuration/variables.html#assigning-values-to-root-module-variables"&gt;assigned properly&lt;/a&gt; before running &lt;code&gt;terraform plan&lt;/code&gt; or &lt;code&gt;terraform apply&lt;/code&gt;.  It is possible that some variables are hiding somewhere unexpectedly, or we just missed one during the assignment and only realize it when we encounter the following message during the &lt;code&gt;terraform plan&lt;/code&gt; phase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bucket_name&lt;/span&gt;
  &lt;span class="nx"&gt;Enter&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;tfvar&lt;/strong&gt; is created to solve the problems mentioned above.  It is a CLI tool that extracts all variables declared in your Terraform configuration and output them in the format of .tfvars file so that you can use it as a template to write the variable definitions.&lt;/p&gt;

&lt;p&gt;In this article, I would like to show you how this tool works.&lt;/p&gt;
&lt;h2&gt;
  
  
  Simple Demo
&lt;/h2&gt;

&lt;p&gt;Let's say we have the following variable declared in our Terraform configuration&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="nx"&gt;bucket_name&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="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"mybucket"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="nx"&gt;instance_name&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="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;running &lt;code&gt;tfvar .&lt;/code&gt; in the root directory of the Terraform configuration will give us the following in the standard output (stdout):&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;tfvar &lt;span class="nb"&gt;.&lt;/span&gt;
bucket_name   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"mybucket"&lt;/span&gt;
instance_name &lt;span class="o"&gt;=&lt;/span&gt; null
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Notice that the default value are automatically assigned in the generated definitions.  If we want to ignore the default values, there is a &lt;code&gt;--ignore-default&lt;/code&gt; flag that we can use.  As shown below the with the &lt;code&gt;--ignore-default&lt;/code&gt; flag, we get &lt;code&gt;null&lt;/code&gt; instead:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;tfvar &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--ignore-default&lt;/span&gt;
bucket_name   &lt;span class="o"&gt;=&lt;/span&gt; null
instance_name &lt;span class="o"&gt;=&lt;/span&gt; null
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;A more common usage of &lt;strong&gt;tfvar&lt;/strong&gt; might be to redirect the stdout to a &lt;a href="https://www.terraform.io/docs/configuration/variables.html#variable-definitions-tfvars-files"&gt;file&lt;/a&gt; e.g. &lt;code&gt;inputs.tfvars&lt;/code&gt; then change the value &lt;code&gt;null&lt;/code&gt; in the file to a desired value.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;tfvar &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--ignore-default&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; inputs.tfvars
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Terraform also allows input variables to be assigned &lt;a href="https://www.terraform.io/docs/configuration/variables.html#environment-variables"&gt;via environment variables&lt;/a&gt;.  For this, &lt;strong&gt;tfvar&lt;/strong&gt; also provides the &lt;code&gt;-e&lt;/code&gt; flag to generate a template in the environment variables format.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;tfvar &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--ignore-default&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TF_VAR_bucket_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TF_VAR_instance_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If we already have some variables defined in &lt;code&gt;terraform.tfvars[.json]&lt;/code&gt;, &lt;code&gt;*.auto.tfvars[.json]&lt;/code&gt;, or environment variables (&lt;code&gt;TF_VAR_&lt;/code&gt; followed by the name of a declared variable), (see section &lt;a href="https://www.terraform.io/docs/configuration/variables.html#assigning-values-to-root-module-variables"&gt;"Assigning Values to Root Module Variables"&lt;/a&gt; in Terraform's documentation) and want to include there defined values in the generated one, we can use the &lt;code&gt;--auto-assign&lt;/code&gt; flag:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TF_VAR_bucket_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"mybucket"&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TF_VAR_instance_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"myinstance"&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;tfvar &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--auto-assign&lt;/span&gt;
bucket_name   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"mybucket"&lt;/span&gt;
instance_name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"myinstance"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Finally for completeness sake, we also provide &lt;code&gt;--var&lt;/code&gt; and &lt;code&gt;--var-file&lt;/code&gt; flags to allow individual variables to be set, just like the &lt;code&gt;-var&lt;/code&gt; and &lt;code&gt;-var-file&lt;/code&gt; that we have in the &lt;code&gt;terraform (plan|apply)&lt;/code&gt; commands.  However, I honestly could not think of any use case that I would need this in my workflow 😆.&lt;/p&gt;

&lt;p&gt;All &lt;code&gt;--auto-assign&lt;/code&gt;, &lt;code&gt;--var&lt;/code&gt;, and &lt;code&gt;--var-file&lt;/code&gt; flags can be used together, and when they are, they follow the variable definition precedence set by &lt;a href="https://www.terraform.io/docs/configuration/variables.html#variable-definition-precedence"&gt;Terraform&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to get it?
&lt;/h2&gt;

&lt;p&gt;The source codes of &lt;strong&gt;tfvar&lt;/strong&gt; is publicly available on GitHub. In the repository below you will find the &lt;a href="https://github.com/shihanng/tfvar#installation"&gt;installation instructions&lt;/a&gt;.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vJ70wriM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/shihanng"&gt;
        shihanng
      &lt;/a&gt; / &lt;a href="https://github.com/shihanng/tfvar"&gt;
        tfvar
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Terraform's variable definitions template generator.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
&lt;code&gt;tfvar&lt;/code&gt;
&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://github.com/shihanng/tfvar/actions?query=workflow%3Amain"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tAqNRkCL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/shihanng/tfvar/workflows/main/badge.svg%3Fbranch%3Dmaster" alt=""&gt;&lt;/a&gt;
&lt;a href="https://github.com/shihanng/tfvar/actions?query=workflow%3Arelease"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jFr5u02v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/shihanng/tfvar/workflows/release/badge.svg%3Fbranch%3Dmaster" alt=""&gt;&lt;/a&gt;
&lt;a href="https://github.com/shihanng/tfvar/releases"&gt;&lt;img src="https://camo.githubusercontent.com/5f0a0aae578bd1d533e7504b57c6d1f2cd0c2dfb/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f73686968616e6e672f7466766172" alt="GitHub release (latest by date)"&gt;&lt;/a&gt;
&lt;a href="https://coveralls.io/github/shihanng/tfvar?branch=master" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/d92272d11f3502eb45734ceecd1775574b0885fc/68747470733a2f2f636f766572616c6c732e696f2f7265706f732f6769746875622f73686968616e6e672f74667661722f62616467652e7376673f6272616e63683d6d6173746572" alt="Coverage Status"&gt;&lt;/a&gt;
&lt;a href="https://goreportcard.com/report/github.com/shihanng/tfvar" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/6042bfa33155be03cbbbcba6ab70c82771543f30/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f73686968616e6e672f7466766172" alt="Go Report Card"&gt;&lt;/a&gt;
&lt;a href="http://godoc.org/github.com/shihanng/tfvar/pkg/tfvar" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/3fc1938e76385d1f990b49074215eaa2becd9d3d/68747470733a2f2f676f646f632e6f72672f6769746875622e636f6d2f73686968616e6e672f74667661722f706b672f74667661723f7374617475732e737667" alt="Package Documentation"&gt;&lt;/a&gt;
&lt;a href="https://github.com/shihanng/tfvar/blob/master/LICENSE"&gt;&lt;img src="https://camo.githubusercontent.com/78d07e762dd0447d0f9c930bb0b688e886235a61/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f73686968616e6e672f7466766172" alt="GitHub license"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;tfvar&lt;/strong&gt; is a &lt;a href="https://www.terraform.io/" rel="nofollow"&gt;Terraform&lt;/a&gt;'s &lt;a href="https://www.terraform.io/docs/configuration/variables.html#assigning-values-to-root-module-variables" rel="nofollow"&gt;variable definitions&lt;/a&gt; template generator.&lt;/p&gt;
&lt;p&gt;For Terraform configuration that has input variables declared, e.g.,&lt;/p&gt;
&lt;div class="highlight highlight-source-terraform"&gt;&lt;pre&gt;&lt;span class="pl-k"&gt;variable&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;image_id&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt; {
  type &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;string&lt;/span&gt;
}
&lt;span class="pl-k"&gt;variable&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;availability_zone_names&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt; {
  type    &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-c1"&gt;list&lt;/span&gt;(&lt;span class="pl-k"&gt;string&lt;/span&gt;)
  default &lt;span class="pl-k"&gt;=&lt;/span&gt; [&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;us-west-1a&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;]
}
&lt;span class="pl-k"&gt;variable&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;docker_ports&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt; {
  type &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-c1"&gt;list&lt;/span&gt;(&lt;span class="pl-v"&gt;object&lt;/span&gt;({
    internal &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;number&lt;/span&gt;
    external &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;number&lt;/span&gt;
    protocol &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;string&lt;/span&gt;
  }))
  default &lt;span class="pl-k"&gt;=&lt;/span&gt; [
    {
      internal &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-c1"&gt;8300&lt;/span&gt;
      external &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-c1"&gt;8300&lt;/span&gt;
      protocol &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;tcp&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
    }
  ]
}&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;tfvar&lt;/strong&gt; will search for all input variables and generate template that helps user populates those variables easily
&lt;pre&gt;&lt;code&gt;$ tfvar
availability_zone_names = ["us-west-1a"]
docker_ports            = [{ external = 8300, internal = 8300, protocol = "tcp" }]
image_id                = null
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Note that default values are assigned to the definitions by default as shown above. Use the &lt;code&gt;--ignore-default&lt;/code&gt; options to ignore the default values
&lt;pre&gt;&lt;code&gt;$ tfvar&lt;/code&gt;&lt;/pre&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/shihanng/tfvar"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;tfvar&lt;/strong&gt; is a tool to solve a trivial problem that would only cost me a few minutes.  However, I decided to spend hours to build it hoping that it will save a few extra minutes off from my fellow Terraformers.  Some of the codes/ideas are borrowed from &lt;a href="https://github.com/hashicorp/terraform"&gt;Terraform itself&lt;/a&gt; which I enjoy exploring a lot.  Please give it a try and I would love hear your feedback (via issues/PRs in project repository or comments in this post). If you like it, give the repository a ⭐ which will help other people discover it.&lt;/p&gt;

&lt;p&gt;Thanks for reading ❤️.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>opensource</category>
      <category>devops</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Improve your PostgreSQL Workflows with vim-tmux-runner</title>
      <dc:creator>Shi Han</dc:creator>
      <pubDate>Fri, 17 Jan 2020 12:53:35 +0000</pubDate>
      <link>https://dev.to/shihanng/improve-your-postgresql-workflows-with-vim-tmux-runner-51g4</link>
      <guid>https://dev.to/shihanng/improve-your-postgresql-workflows-with-vim-tmux-runner-51g4</guid>
      <description>&lt;p&gt;There are many client applications for working with databases (PostgreSQL, etc.) available in the market.  Some of my colleagues use &lt;a href="https://www.jetbrains.com/datagrip/" rel="noopener noreferrer"&gt;JetBrains's DataGrip&lt;/a&gt;, &lt;a href="https://eggerapps.at/postico/" rel="noopener noreferrer"&gt;Postico&lt;/a&gt;, &lt;a href="https://dbeaver.io/" rel="noopener noreferrer"&gt;DBeaver&lt;/a&gt;, etc. While I do not work much directly with DBs on daily basis, I do sometimes find myself experimenting on local PostgreSQL instance via &lt;a href="https://www.pgcli.com/" rel="noopener noreferrer"&gt;&lt;code&gt;pgcli&lt;/code&gt;&lt;/a&gt; before "transferring" the queries into application code. During this process, one feature that I would really like to have from those GUI clients is the ability&lt;br&gt;
execute selected/highlighted commands directly from the editor and get the result directly on screen. I use Vim as my main editor. So it would be nice if I can perform the same thing describe above&lt;br&gt;
within Vim.&lt;/p&gt;

&lt;p&gt;It turns out this can be achieve with combination of Vim and &lt;a href="https://github.com/tmux/tmux" rel="noopener noreferrer"&gt;tmux&lt;/a&gt; and the &lt;strong&gt;vim-tmux-runner&lt;/strong&gt; plugin. I've included a small demo in this article to show how that can be done with the tools mentioned before.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/christoomey" rel="noopener noreferrer"&gt;
        christoomey
      &lt;/a&gt; / &lt;a href="https://github.com/christoomey/vim-tmux-runner" rel="noopener noreferrer"&gt;
        vim-tmux-runner
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Vim and tmux, sittin' in a tree...
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;VTR [Vim Tmux Runner]&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A simple, vimscript only, command runner for sending commands from vim to tmux.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Usage&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;VTR provides a handful of commands for managing and interacting with &lt;a href="http://tmux.sourceforge.net/" rel="nofollow noopener noreferrer"&gt;tmux&lt;/a&gt;
the terminal multiplexer. The main command is:&lt;/p&gt;
&lt;div class="highlight highlight-source-viml notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;VtrSendCommandToRunner&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;This command will prompt for a command to run, then send it to the runner pane
for execution. Subsequent calls to &lt;code&gt;VtrSendCommandToRunner&lt;/code&gt; will reuse the
provided command.&lt;/p&gt;
&lt;p&gt;If you would like VTR to create a runner pane if one doesn't exist while issuing
a command, a bang version can be used: &lt;code&gt;VtrSendCommandToRunner!&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;VTR provides configuration options that allow for control over the size and
location of the VTR runner pane. In addition, VTR provides commands to resize,
reorient, and even detach the runner pane making the interaction as painless as
possible.&lt;/p&gt;
&lt;p&gt;For a complete summary of the available commands and configuration options in
VTR, check &lt;a href="https://github.com/christoomey/vim-tmux-runner/blob/master/doc/vim-tmux-runner.txt" rel="noopener noreferrer"&gt;the included doc file&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/christoomey/vim-tmux-runner" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  DB Preparation
&lt;/h3&gt;

&lt;p&gt;For demo purposes we are going to setup a sample PostgreSQL database in a Docker container. If you already have a DB to play with, feel free to skip this part.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; postgres_demo &lt;span class="nt"&gt;-p&lt;/span&gt; 5432:5432 &lt;span class="nt"&gt;--rm&lt;/span&gt; postgres:11.6
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"CREATE DATABASE pagila;"&lt;/span&gt; | psql &lt;span class="nt"&gt;-h&lt;/span&gt; localhost &lt;span class="nt"&gt;-p&lt;/span&gt; 5432 &lt;span class="nt"&gt;-U&lt;/span&gt; postgres
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://raw.githubusercontent.com/devrimgunduz/pagila/68b7bab066a18988694a8b698533c3c507f7b133/pagila-schema.sql | psql &lt;span class="nt"&gt;-h&lt;/span&gt; localhost &lt;span class="nt"&gt;-p&lt;/span&gt; 5432 &lt;span class="nt"&gt;-U&lt;/span&gt; postgres pagila &lt;span class="nt"&gt;-f&lt;/span&gt; -
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://raw.githubusercontent.com/devrimgunduz/pagila/68b7bab066a18988694a8b698533c3c507f7b133/pagila-data.sql | psql &lt;span class="nt"&gt;-h&lt;/span&gt; localhost &lt;span class="nt"&gt;-p&lt;/span&gt; 5432 &lt;span class="nt"&gt;-U&lt;/span&gt; postgres pagila &lt;span class="nt"&gt;-f&lt;/span&gt; -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Vim configurations
&lt;/h3&gt;

&lt;p&gt;First install the &lt;code&gt;christoomey/vim-tmux-runner&lt;/code&gt; plugin with your favorite Vim plugin manager. Then, for my setup, I've included the following in my vimrc:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;g:VtrUseVtrMaps&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;g:VtrStripLeadingWhitespace&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;g:VtrClearEmptyLines&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  DEMO
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;This workflow requires a &lt;strong&gt;tmux&lt;/strong&gt; session, so go ahead and start up one.&lt;/li&gt;
&lt;li&gt;Then we launch &lt;strong&gt;Vim&lt;/strong&gt; within that tmux session.&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;Vim&lt;/strong&gt;, create a new tmux pane (referred as "runner" in the plugin) using the &lt;code&gt;:VtrOpenRunner&lt;/code&gt; command.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run &lt;strong&gt;pgcli&lt;/strong&gt; in the newly created pane and connect to the DB (&lt;code&gt;pagila&lt;/code&gt;) created above.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pgcli &lt;span class="nt"&gt;-h&lt;/span&gt; localhost &lt;span class="nt"&gt;-p&lt;/span&gt; 5432 &lt;span class="nt"&gt;-u&lt;/span&gt; postgres pagila
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Turn on &lt;a href="https://www.pgcli.com/multi-line" rel="noopener noreferrer"&gt;&lt;strong&gt;Multi-line Mode&lt;/strong&gt;&lt;/a&gt; in &lt;strong&gt;pgcli&lt;/strong&gt; by pressing the &lt;code&gt;&amp;lt;F3&amp;gt;&lt;/code&gt; key. This means that our PostgreSQL commands in Vim need to end with semi-colon &lt;code&gt;;&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;


&lt;div class="ltag_asciinema"&gt;
  
&lt;/div&gt;



&lt;p&gt;Once we have &lt;strong&gt;pgcli&lt;/strong&gt; running on the runner pane, we can write down our SQL query in Vim then select the query in &lt;a href="http://vimdoc.sourceforge.net/htmldoc/visual.html" rel="noopener noreferrer"&gt;Visual mode&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;film_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;film&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Finally use the binding &lt;code&gt;&amp;lt;Leader&amp;gt;sl&lt;/code&gt; to send the selected SQL commands to the runner. The binding (enable via &lt;code&gt;g:VtrUseVtrMaps&lt;/code&gt; mentioned above) executes command &lt;code&gt;:VtrSendLinesToRunner&lt;/code&gt;.&lt;/p&gt;


&lt;div class="ltag_asciinema"&gt;
  
&lt;/div&gt;



&lt;p&gt;Often, we might be in a state where we already have &lt;strong&gt;pgcli&lt;/strong&gt; opened but not attached to Vim as the runner. In this situation, if we do &lt;code&gt;:VtrSendLinesToRunner&lt;/code&gt;, we will get &lt;code&gt;VTR: No runner pane attached.&lt;/code&gt; error. For this case, we can use &lt;code&gt;:VtrAttachToPane&lt;/code&gt; command to attach the pane as runner then continue with our workflow.&lt;/p&gt;


&lt;div class="ltag_asciinema"&gt;
  
&lt;/div&gt;


&lt;p&gt;This concludes the article about me indulging myself with another "How to do X in Vim?" Of course, usage of &lt;a href="https://github.com/christoomey/vim-tmux-runner" rel="noopener noreferrer"&gt;&lt;strong&gt;vim-tmux-runner&lt;/strong&gt;&lt;/a&gt; is not limited to PostgreSQL but you can do the same on other REPL shell e.g. &lt;a href="https://en.wikipedia.org/wiki/Interactive_Ruby_Shell" rel="noopener noreferrer"&gt;IRB&lt;/a&gt;, &lt;a href="https://ipython.org/index.html" rel="noopener noreferrer"&gt;IPython&lt;/a&gt;, etc. Hope this article gives you some ideas of how to integrate this plugin into your workflow. Thanks for reading ❤️.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cleanup 🗑️
&lt;/h3&gt;

&lt;p&gt;If you want to clean up the demo DB created above, simply do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker stop postgres_demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>productivity</category>
      <category>tutorial</category>
      <category>vim</category>
      <category>tmux</category>
    </item>
    <item>
      <title>gig: a gitignore generator</title>
      <dc:creator>Shi Han</dc:creator>
      <pubDate>Sun, 15 Dec 2019 10:24:48 +0000</pubDate>
      <link>https://dev.to/shihanng/gig-a-gitignore-generator-opc</link>
      <guid>https://dev.to/shihanng/gig-a-gitignore-generator-opc</guid>
      <description>&lt;p&gt;If you are using &lt;a href="https://git-scm.com/" rel="noopener noreferrer"&gt;&lt;code&gt;git&lt;/code&gt;&lt;/a&gt; in your project, you probably know what is &lt;code&gt;.gitignore&lt;/code&gt;. Since I never seem to be able to memorized gitignore's &lt;a href="https://git-scm.com/docs/gitignore#_pattern_format" rel="noopener noreferrer"&gt;pattern format&lt;/a&gt; correctly, when I needed to write a gitignore file for a specific project, I'll visit &lt;a href="https://github.com/github/gitignore" rel="noopener noreferrer"&gt;github/gitignore&lt;/a&gt; in my web browser, search for gitignore file, open the file, copy its content, then finally paste it into my editor...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fshihanng%2Fdev.to%2Fmaster%2Fposts%2Fgig%2Fassets%2Fgithub_gitignore.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fshihanng%2Fdev.to%2Fmaster%2Fposts%2Fgig%2Fassets%2Fgithub_gitignore.gif" alt="Using github/gitignore"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As we can imagine, if you need to create gitignore file for multiple programming languages the process becomes very tedious.&lt;/p&gt;

&lt;p&gt;A while back I was introduced to &lt;a href="https://www.gitignore.io/" rel="noopener noreferrer"&gt;gitignore.io&lt;/a&gt; which is a web service that allows us to create gitignore file easily for multiple languages. &lt;a href="https://www.gitignore.io/" rel="noopener noreferrer"&gt;gitignore.io&lt;/a&gt; also provides API access and very nice &lt;a href="https://docs.gitignore.io/install/command-line" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; on how to run gitignore.io from your command line. It was a huge upgrade and I've been using this tool since then. I even submitted a PR to gitignore &lt;a href="https://github.com/direnv/direnv" rel="noopener noreferrer"&gt;direnv&lt;/a&gt;:&lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/toptal/gitignore/pull/241" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        Add direnv.gitignore
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#241&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/shihanng" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars3.githubusercontent.com%2Fu%2F7593229%3Fv%3D4" alt="shihanng avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/shihanng" rel="noopener noreferrer"&gt;shihanng&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/toptal/gitignore/pull/241" rel="noopener noreferrer"&gt;&lt;time&gt;Oct 03, 2019&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;Based on &lt;a href="https://github.com/direnv/direnv/wiki/Git" rel="noopener noreferrer"&gt;https://github.com/direnv/direnv/wiki/Git&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Pull Request&lt;/h1&gt;
&lt;p&gt;Thank you for contributing to @dvcs/gitignore and &lt;a href="https://www.gitignore.io" rel="nofollow noopener noreferrer"&gt;https://www.gitignore.io&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;New or update&lt;/h2&gt;
&lt;p&gt;Select the appropriate check box for this pull request. This helps when merging to ensure there are no conflicts with other templates or misunderstandings of how thee template list works.&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;New&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[x] Template - New &lt;code&gt;.gitignore&lt;/code&gt; template&lt;/li&gt;
&lt;li&gt;[ ] Composition - Template made from smaller templates&lt;/li&gt;
&lt;li&gt;[ ] Inheritance - Template similar to an existing template&lt;/li&gt;
&lt;li&gt;[ ] Patch - Template extending functionality of existing template&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Update&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] Template - Update existing &lt;code&gt;.gitignore&lt;/code&gt; template&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Details&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/direnv/direnv" rel="noopener noreferrer"&gt;direnv&lt;/a&gt; is a tool that can load and unload environment variables based on the current working directory. It is commonly advised to not to include its configuration file &lt;code&gt;.envrc&lt;/code&gt; into a public repository therefore having this in the gitignore will be very handy. The values are based on this documentation: &lt;a href="https://github.com/direnv/direnv/wiki/Git" rel="noopener noreferrer"&gt;https://github.com/direnv/direnv/wiki/Git&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Thank you.&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/toptal/gitignore/pull/241" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;However, after the PR got merged, I found out that the change is not reflected in &lt;a href="https://www.gitignore.io/" rel="noopener noreferrer"&gt;gitignore.io&lt;/a&gt; (yet). My guess is that the maintainer needs to "redeploy" the site in order to include the latest collections.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ curl https://gitignore.io/api/direnv

# Created by https://www.gitignore.io/api/direnv
# Edit at https://www.gitignore.io/?templates=direnv

#!! ERROR: direnv is undefined. Use list command to see defined gitignore types !!#

# End of https://www.gitignore.io/api/direnv


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;gitignore.io's collections of gitignore templates are maintained in a separated repository: &lt;a href="https://github.com/toptal/gitignore" rel="noopener noreferrer"&gt;github.com/toptal/gitignore&lt;/a&gt;. They are extensions of &lt;a href="https://github.com/github/gitignore" rel="noopener noreferrer"&gt;github/gitignore&lt;/a&gt; that include additional types known as &lt;code&gt;.patch&lt;/code&gt;, &lt;code&gt;.stack&lt;/code&gt;. When I bumped into the error above, I wondered if I can somehow generate the gitignore file using the repository directly without the web service. Which then lead me deep into the rabbit hole and created &lt;strong&gt;gig&lt;/strong&gt;...&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/shihanng" rel="noopener noreferrer"&gt;
        shihanng
      &lt;/a&gt; / &lt;a href="https://github.com/shihanng/gig" rel="noopener noreferrer"&gt;
        gig
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Generate .gitignore files from your terminal (mostly) offline!
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;
&lt;code&gt;gig&lt;/code&gt; -- .gitignore generator&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/shihanng/gig/actions?query=workflow%3Amain" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/shihanng/gig/workflows/main/badge.svg?branch=develop" alt=""&gt;&lt;/a&gt;
&lt;a href="https://github.com/shihanng/gig/actions?query=workflow%3Arelease" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/shihanng/gig/workflows/release/badge.svg?branch=develop" alt=""&gt;&lt;/a&gt;
&lt;a href="https://github.com/shihanng/gig/blob/develop/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/9c70ca3d6cc1ff86cff03ba37492da4a24c8e4e179c170a77739e89e1ddec5fd/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f73686968616e6e672f676967" alt="GitHub"&gt;&lt;/a&gt;
&lt;a href="https://github.com/shihanng/gig/releases" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/63933cacd5a485abe4a948dbf7a8d127d09eaeb64ea4e34f5ee78c8554f8514c/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f73686968616e6e672f676967" alt="GitHub release (latest by date)"&gt;&lt;/a&gt;
&lt;a href="https://goreportcard.com/report/github.com/shihanng/gig" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/9d1a2fc234f491f0b16440ea58b7521e8b66ac9d1d3d271022020c7f1a33d255/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f73686968616e6e672f676967" alt="Go Report Card"&gt;&lt;/a&gt;
&lt;a href="https://coveralls.io/github/shihanng/gig?branch=develop" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/d8a34dfa119ff124e667a4953a3abcf239db2698467ee2da6cb244dbfeb7534e/68747470733a2f2f636f766572616c6c732e696f2f7265706f732f6769746875622f73686968616e6e672f6769672f62616467652e7376673f6272616e63683d646576656c6f70" alt="Coverage Status"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;gig&lt;/code&gt; is a command line tool to help you create useful &lt;code&gt;.gitignore&lt;/code&gt; files for your project
It is inspired by &lt;a href="https://www.gitignore.io/" rel="nofollow noopener noreferrer"&gt;gitignore.io&lt;/a&gt; and make use of
the large collection of useful &lt;a href="https://github.com/toptal/gitignore" rel="noopener noreferrer"&gt;&lt;code&gt;.gitignore&lt;/code&gt; templates&lt;/a&gt; of the web service
This also means that &lt;code&gt;gig&lt;/code&gt; supports the are four file types that gitignore.io recognizes.
Content generated by &lt;code&gt;gig&lt;/code&gt; should match the one generated by &lt;a href="https://www.gitignore.io/" rel="nofollow noopener noreferrer"&gt;gitignore.io&lt;/a&gt; except the
order of stacks in which &lt;a href="https://www.gitignore.io/" rel="nofollow noopener noreferrer"&gt;gitignore.io&lt;/a&gt; does not seem to guarantee any.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Motivation&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Prior to this project, I used to have one of &lt;a href="https://docs.gitignore.io/install/command-line" rel="nofollow noopener noreferrer"&gt;these command lines&lt;/a&gt; in my &lt;code&gt;.zshrc&lt;/code&gt;.
However, problems I have with this command line are that&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I won't be able to use it without internet access&lt;/li&gt;
&lt;li&gt;It seems &lt;a href="https://www.gitignore.io/" rel="nofollow noopener noreferrer"&gt;gitignore.io&lt;/a&gt; that is not using the latest templates in &lt;a href="https://github.com/toptal/gitignore" rel="noopener noreferrer"&gt;https://github.com/toptal/gitignore&lt;/a&gt;. The project needs to update to the latest git submodule then deploy it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Therefore this tool is created to solve…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/shihanng/gig" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Introducing &lt;strong&gt;gig&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;gig&lt;/strong&gt; is a CLI port of &lt;a href="https://www.gitignore.io/" rel="noopener noreferrer"&gt;gitignore.io&lt;/a&gt; and make use of the &lt;a href="https://github.com/toptal/gitignore" rel="noopener noreferrer"&gt;large collection of .gitignore templates of gitignore.io&lt;/a&gt; mentioned above. This also means that &lt;strong&gt;gig&lt;/strong&gt; supports all four file types that gitignore.io recognizes and generates contents of gitignore like gitignore.io does.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing
&lt;/h3&gt;

&lt;p&gt;If you are on macOS and are using Homebrew,&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

brew install shihanng/gig/gig


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;For other platforms, download the pre-built binaries from the &lt;a href="https://github.com/shihanng/gig/releases" rel="noopener noreferrer"&gt;release page&lt;/a&gt; then place the binary in the &lt;code&gt;$PATH&lt;/code&gt; e.g. &lt;code&gt;/usr/local/bin&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Using
&lt;/h3&gt;

&lt;p&gt;The most direct way of using &lt;strong&gt;gig&lt;/strong&gt; is to use the &lt;code&gt;gen&lt;/code&gt; subcommand with a list of template names as arguments, e.g.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

gig gen go terraform &amp;gt; .gitignore


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Example above generates gitignore templates for &lt;a href="https://golang.org/" rel="noopener noreferrer"&gt;Go&lt;/a&gt; and &lt;a href="https://www.terraform.io/" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt; then redirects those into &lt;code&gt;.gitignore&lt;/code&gt; file. With additional &lt;code&gt;-f&lt;/code&gt; flag, we can omit the redirect part of the command. &lt;strong&gt;gig&lt;/strong&gt; command will cache the content of &lt;a href="https://github.com/toptal/gitignore" rel="noopener noreferrer"&gt;github.com/toptal/gitignore&lt;/a&gt; repository into &lt;code&gt;$XDG_CACHE_HOME/gig&lt;/code&gt; during the first run. This means that the next execute should work offline.&lt;/p&gt;


&lt;div class="ltag_asciinema"&gt;
  
&lt;/div&gt;



&lt;p&gt;When we want to look for available templates, we can use the &lt;code&gt;list command&lt;/code&gt;. It's a pretty long list, so it works better with a pager:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

gig list | less


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;There is also a &lt;code&gt;search&lt;/code&gt; command which calls &lt;a href="https://github.com/junegunn/fzf" rel="noopener noreferrer"&gt;&lt;code&gt;fzf -m&lt;/code&gt;&lt;/a&gt; internally on the list above and allows user to select (by pressing &lt;code&gt;tab&lt;/code&gt; by default) the templates in fzf.&lt;/p&gt;


&lt;div class="ltag_asciinema"&gt;
  
&lt;/div&gt;



&lt;p&gt;Finally there is the least useful &lt;code&gt;autogen&lt;/code&gt; subcommand which was added just for fun.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

gig autogen


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;It will look into the current directory and try to figure out what to gitignore. Internally it uses &lt;a href="https://github.com/src-d/enry" rel="noopener noreferrer"&gt;source{d}'s enry package&lt;/a&gt; for programming language detection (which itself is a port of &lt;a href="https://github.com/github/linguist" rel="noopener noreferrer"&gt;GitHub's linguist&lt;/a&gt;). Unfortunately it does not always work as expected:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is not able to detect framework, e.g. &lt;code&gt;.js&lt;/code&gt; files will be detected as &lt;code&gt;javascript&lt;/code&gt; but there is not &lt;strong&gt;JavaScript&lt;/strong&gt; gitignore template available.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;go.mod&lt;/code&gt; and &lt;code&gt;go.sum&lt;/code&gt; files will be detected as &lt;a href="https://github.com/github/linguist/blob/643c091e8d1e8c20401d1267ca558d304ebae8ca/lib/linguist/languages.yml#L5337-L5338" rel="noopener noreferrer"&gt;&lt;strong&gt;text&lt;/strong&gt;&lt;/a&gt; for the reason mentioned in &lt;a href="https://github.com/github/linguist/pull/4340" rel="noopener noreferrer"&gt;this PR&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;gig&lt;/strong&gt; started as a fun project to see how far I can go to build a CLI tool with the remix of&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/spf13/cobra" rel="noopener noreferrer"&gt;github.com/spf13/cobra&lt;/a&gt;: Go's framework for CLI,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/src-d/go-git" rel="noopener noreferrer"&gt;github.com/src-d/go-git&lt;/a&gt;: Git implementation in Go,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/src-d/enry" rel="noopener noreferrer"&gt;github.com/src-d/enry&lt;/a&gt;: for programming language detection,&lt;/li&gt;
&lt;li&gt;and many other libraries...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now it is has become a tool that I will use it when starting a new project.&lt;/p&gt;

&lt;p&gt;Please give it a try and I would love hear your feedback (via issues/PRs in &lt;a href="https://github.com/shihanng/gig" rel="noopener noreferrer"&gt;project repository&lt;/a&gt; or comments in this post). If you like it, give it a ⭐ so that other people will hear about it.&lt;/p&gt;

&lt;p&gt;Thanks for reading ❤️&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>showdev</category>
      <category>opensource</category>
      <category>go</category>
    </item>
    <item>
      <title>Managing Go Versions with direnv</title>
      <dc:creator>Shi Han</dc:creator>
      <pubDate>Wed, 14 Aug 2019 13:52:46 +0000</pubDate>
      <link>https://dev.to/shihanng/managing-go-versions-with-direnv-19mb</link>
      <guid>https://dev.to/shihanng/managing-go-versions-with-direnv-19mb</guid>
      <description>&lt;p&gt;Recently I've joined a project that requires previous version of Go. Since I have the latest version already installed on my machine I was looking for a way to manage different versions of Go in a single machine. In Python world, there is a very well-known version management called &lt;a href="https://github.com/pyenv/pyenv"&gt;pyenv&lt;/a&gt; which is highly inspired by the Ruby's one: &lt;a href="https://github.com/rbenv/rbenv"&gt;rbenv&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As for Go, it seems that there is no authoritative way to manage the versions of Go. Probably it is not a big issue as many would just upgrade to the latest version of the language since it essentially &lt;a href="https://golang.org/doc/go1compat"&gt;promises that new minor version of 1.X would not break the older ones&lt;/a&gt;. Yet, through a very brief Googling, I found that there are three main approaches for this problem:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Installing each version of Go from source. This &lt;a href="https://medium.com/@vCabbage/go-installing-multiple-go-versions-from-source-db5573067c"&gt;article by Kale Blankenship&lt;/a&gt; describe the steps in details.&lt;/li&gt;
&lt;li&gt;There is also a tool that is similar to &lt;strong&gt;pyenv&lt;/strong&gt; and &lt;strong&gt;rbenv&lt;/strong&gt; available in the Go ecosystem known as &lt;a href="https://github.com/moovweb/gvm"&gt;gvm&lt;/a&gt; It also comes with additional features such as managing different locations of &lt;code&gt;GOPATH&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Lastly, we can also &lt;a href="https://golang.org/doc/install#extra_versions"&gt;use &lt;code&gt;go get&lt;/code&gt;&lt;/a&gt;. Yes! GO GET!&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Installing a Different Version of Go
&lt;/h2&gt;

&lt;p&gt;In this post, we will explore how to use &lt;code&gt;go get&lt;/code&gt; to manage different versions Go. One small caveat for this approach is that not all Go versions are support. Supported versions are listed on &lt;a href="https://godoc.org/golang.org/dl"&gt;the download page&lt;/a&gt;. Let's say that we need &lt;code&gt;go1.8.7&lt;/code&gt;, all we have to do is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;go get golang.org/dl/go1.8.7
&lt;span class="nv"&gt;$ &lt;/span&gt;go1.8.7 download
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Once the download is complete, we can compile our source code with &lt;code&gt;v1.8.7&lt;/code&gt; using&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;go1.8.7 version
go version go1.8.7 darwin/amd64
&lt;span class="nv"&gt;$ &lt;/span&gt;go1.8.7 build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Uninstalling can be done by removing the &lt;code&gt;GOROOT&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;go1.8.7 &lt;span class="nb"&gt;env &lt;/span&gt;GOROOT&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Using &lt;code&gt;goX.Y.Z&lt;/code&gt; as &lt;code&gt;go&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Often in a project we have some kind of build scripts of Makefiles which will call &lt;code&gt;go&lt;/code&gt; instead of &lt;code&gt;go1.8.7&lt;/code&gt; to build our Go binary. Therefore it would be very convenient if we can use the executable &lt;code&gt;go1.8.7&lt;/code&gt; as &lt;code&gt;go&lt;/code&gt; for a specific directory/project. This is where &lt;a href="https://github.com/direnv/direnv"&gt;&lt;strong&gt;direnv&lt;/strong&gt;&lt;/a&gt; comes into play.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;direnv is an environment switcher for the shell. It knows how to hook into&lt;br&gt;
bash, zsh, tcsh, fish shell and elvish to load or unload environment&lt;br&gt;
variables depending on the current directory&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For demostration purposes, we will create a new project in the &lt;code&gt;GOPATH&lt;/code&gt; as following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GOPATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;go1.8.7 &lt;span class="nb"&gt;env &lt;/span&gt;GOPATH&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GOPATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/src/github.com/user/hello"&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GOPATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/src/github.com/user/hello"&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;main.go &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOL&lt;/span&gt;&lt;span class="sh"&gt;
package main

import "fmt"

func main() {
    fmt.Println("Hello, world.")
}
&lt;/span&gt;&lt;span class="no"&gt;EOL
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In order to enable &lt;strong&gt;direnv&lt;/strong&gt; in our project directory, we need to setup the &lt;code&gt;.envrc&lt;/code&gt; file. This can be done by doing&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;direnv edit &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;inside the root directory of our project. The content of &lt;code&gt;.envrc&lt;/code&gt; file should be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export GOROOT="$(go1.8.7 env GOROOT)"
PATH_add "$(go1.8.7 env GOROOT)/bin"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In the first line, we point &lt;code&gt;GOROOT&lt;/code&gt; to the version that we want to work with. Then we add the Go 1.8.7 compiler into &lt;code&gt;PATH&lt;/code&gt; so that it will be found before the default one. Once the setup is done, running &lt;code&gt;go version&lt;/code&gt; will give us the older version.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;go version
go version go1.8.7 linux/amd64
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We can then build the &lt;code&gt;main.go&lt;/code&gt; using&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ go build
$ gdb ./hello
...
(gdb) p 'runtime.buildVersion'
$1 = 0x4a5208 "go1.8.7"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The above also shows how to check the which version of Go is used to build the &lt;code&gt;hello&lt;/code&gt; binary as described in &lt;a href="https://dave.cheney.net/2017/06/20/how-to-find-out-which-go-version-built-your-binary"&gt;this post&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;That's all for the demo. Hope you find this post useful. Personally I think that this approach has a good balance between simplicity and usability. One other thing that I often find useful in tools like &lt;code&gt;pyenv&lt;/code&gt; is the &lt;code&gt;pyenv install --list&lt;/code&gt; subcommand which shows the available versions that can be installed with &lt;code&gt;pyenv&lt;/code&gt;. Of course the approach above does not have this feature. One hack we can use to list all available versions on terminal is by using the &lt;a href="https://github.com/ericchiang/pup"&gt;&lt;code&gt;pup&lt;/code&gt;&lt;/a&gt; command with &lt;code&gt;curl&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl -s https://godoc.org/golang.org/dl | pup 'body &amp;gt; div.container &amp;gt; table &amp;gt; tbody &amp;gt; tr &amp;gt; td:nth-child(1) &amp;gt; a text{}'

go1.10
go1.10.1
go1.10.2
go1.10.3
go1.10.4
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;It's not pretty but it does the trick 💪.&lt;/p&gt;

&lt;h4&gt;
  
  
  Found a typo?
&lt;/h4&gt;

&lt;p&gt;Thank you for reading! Found a typo? See something that could be improved or anything else that should be updated on this blog post? Thanks to &lt;a href="https://github.com/maxime1992/dev.to"&gt;this project&lt;/a&gt;, you can easily create a pull request on &lt;a href="https://github.com/shihanng/dev.to"&gt;https://github.com/shihanng/dev.to&lt;/a&gt; to propose a fix!&lt;/p&gt;

</description>
      <category>go</category>
      <category>tutorial</category>
      <category>productivity</category>
      <category>direnv</category>
    </item>
    <item>
      <title>Looking into PostgreSQL DB's tables of your Development Environment</title>
      <dc:creator>Shi Han</dc:creator>
      <pubDate>Mon, 15 Jul 2019 07:46:56 +0000</pubDate>
      <link>https://dev.to/shihanng/looking-into-postgresql-db-s-tables-of-your-development-environment-1i0i</link>
      <guid>https://dev.to/shihanng/looking-into-postgresql-db-s-tables-of-your-development-environment-1i0i</guid>
      <description>&lt;p&gt;You just joined an established project and have been given access to the source code. Sometimes we find that it is easier to understand the structure of the project by looking into the database tables and the relations between them. Recently I found myself in this situation with a project that can be built &lt;strong&gt;locally&lt;/strong&gt; with &lt;a href="https://docs.docker.com/compose/" rel="noopener noreferrer"&gt;Docker Compose&lt;/a&gt;. This is a note about what I did to glimpse in the project's tables.&lt;/p&gt;

&lt;h1&gt;
  
  
  Setup
&lt;/h1&gt;

&lt;p&gt;In order to allow readers to follow along for a more hands-on experience, we are going to use the Dockerize Rails app created by &lt;a href="https://github.com/ledermann" rel="noopener noreferrer"&gt;Georg Ledermann&lt;/a&gt; as the "established project." The project already has a &lt;a href="https://github.com/ledermann/docker-rails/blob/c7e0e5d8be469638d21cd13d500d4e5cd4873f8e/docker-compose.yml" rel="noopener noreferrer"&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt;&lt;/a&gt; setup that connects a PostgreSQL database to a Ruby on Rails application.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/ledermann" rel="noopener noreferrer"&gt;
        ledermann
      &lt;/a&gt; / &lt;a href="https://github.com/ledermann/docker-rails" rel="noopener noreferrer"&gt;
        docker-rails
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Dockerize Rails 7 with ActionCable, Webpacker, Stimulus, Elasticsearch, Sidekiq
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Docker-Rails&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Simple Rails 7.0 application to demonstrate using Docker for production deployment. The application is a very simple kind of CMS (content management system) allowing to manage posts. Beside the boring &lt;a href="https://en.wikipedia.org/wiki/Create,_read,_update_and_delete" rel="nofollow noopener noreferrer"&gt;CRUD&lt;/a&gt; functionality it has some non-default features.&lt;/p&gt;
&lt;p&gt;This project aims to build a lean Docker image for use in production. Therefore it's based on the official Alpine Ruby image, uses multi-stage building and some &lt;a href="https://ledermann.dev/blog/2018/04/19/dockerize-rails-the-lean-way/" rel="nofollow noopener noreferrer"&gt;optimizations that I described in my blog&lt;/a&gt;. This results in an image size of ~80MB.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Auto refresh via &lt;a href="https://github.com/rails/rails/tree/master/actioncable" rel="noopener noreferrer"&gt;ActionCable&lt;/a&gt;: If a displayed post gets changed by another user/instance, it refreshes automatically using the publish/subscribe pattern&lt;/li&gt;
&lt;li&gt;Full text search via &lt;a href="https://opensearch.org/" rel="nofollow noopener noreferrer"&gt;OpenSearch&lt;/a&gt; and the &lt;a href="https://github.com/ankane/searchkick" rel="noopener noreferrer"&gt;Searchkick&lt;/a&gt; gem to find post content (with suggestions)&lt;/li&gt;
&lt;li&gt;Autocompletion with &lt;a href="https://github.com/kraaden/autocomplete" rel="noopener noreferrer"&gt;autocompleter&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Editing HTML content with the WYSIWYG JavaScript editor &lt;a href="https://github.com/basecamp/trix" rel="noopener noreferrer"&gt;Trix&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Uploading images directly to S3 with the &lt;a href="https://github.com/janko-m/shrine" rel="noopener noreferrer"&gt;Shrine&lt;/a&gt; gem and &lt;a href="https://github.com/blueimp/jQuery-File-Upload" rel="noopener noreferrer"&gt;jQuery-File-Upload&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Background jobs with &lt;a href="https://github.com/rails/rails/tree/master/activejob" rel="noopener noreferrer"&gt;ActiveJob&lt;/a&gt; and the &lt;a href="http://sidekiq.org/" rel="nofollow noopener noreferrer"&gt;Sidekiq&lt;/a&gt; gem (to…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ledermann/docker-rails" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Let's setup the project locally by referring to the &lt;a href="https://github.com/ledermann/docker-rails/tree/c7e0e5d8be469638d21cd13d500d4e5cd4873f8e#check-it-out" rel="noopener noreferrer"&gt;Check it out!&lt;/a&gt; section of the repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git clone https://github.com/ledermann/docker-rails.git
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;docker-rails
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker-compose build
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker-compose run app yarn &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, you should have a few services running which you can check with the &lt;code&gt;docker-compose ps&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker-compose ps
&lt;span class="go"&gt;            Name                          Command               State            Ports
-----------------------------------------------------------------------------------------------
&lt;/span&gt;&lt;span class="gp"&gt;docker-rails_app_1             docker/startup.sh                Up      0.0.0.0:32774-&amp;gt;&lt;/span&gt;3000/tcp
&lt;span class="go"&gt;docker-rails_db_1              docker-entrypoint.sh postgres    Up      5432/tcp
docker-rails_elasticsearch_1   /usr/local/bin/docker-entr ...   Up      9200/tcp, 9300/tcp
docker-rails_redis_1           docker-entrypoint.sh redis ...   Up      6379/tcp
docker-rails_worker_1          bundle exec sidekiq              Up      3000/tcp
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to checkout the web application that is running on your host, visit &lt;a href="http://localhost:32774" rel="noopener noreferrer"&gt;http://localhost:32774&lt;/a&gt;. Note that the value &lt;code&gt;32774&lt;/code&gt; comes from the &lt;code&gt;Ports&lt;/code&gt; column of &lt;code&gt;docker-rails_app_1&lt;/code&gt; in the output of the &lt;code&gt;docker-compose ps&lt;/code&gt;: container's port &lt;code&gt;3000&lt;/code&gt; is exposed on the host's port &lt;code&gt;32774&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhsqrsctqdt23h6zmraia.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhsqrsctqdt23h6zmraia.png" alt="The demo Docker-Rails app" width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Accessing the Database
&lt;/h1&gt;

&lt;p&gt;Here we will show how to access to the development database using two different tools:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://www.pgcli.com/" rel="noopener noreferrer"&gt;pgcli&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/schemaspy/schemaspy" rel="noopener noreferrer"&gt;SchemaSpy&lt;/a&gt; for a more visualized approach.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before doing so, we need to know the credentials for the database which can be found on the &lt;a href="https://github.com/ledermann/docker-rails/blob/c7e0e5d8be469638d21cd13d500d4e5cd4873f8e/docker-compose.yml#L33-L34" rel="noopener noreferrer"&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt;&lt;/a&gt;. The database that we are interested in is &lt;a href="https://github.com/ledermann/docker-rails/blob/c7e0e5d8be469638d21cd13d500d4e5cd4873f8e/config/database.yml#L27-L29" rel="noopener noreferrer"&gt;&lt;code&gt;docker-rails_development&lt;/code&gt;&lt;/a&gt;. Let's export these value as environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DB_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;foobar123
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DB_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;docker-rails_development
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  pgcli
&lt;/h2&gt;

&lt;p&gt;Install &lt;code&gt;pgcli&lt;/code&gt; based on the instructions in &lt;a href="https://www.pgcli.com/install" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt;. Based on the information below we want to connect to the database (&lt;code&gt;docker-rails_db_1&lt;/code&gt;) on port &lt;code&gt;5432&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker-compose ps db
&lt;span class="go"&gt;      Name                     Command              State    Ports
--------------------------------------------------------------------
docker-rails_db_1   docker-entrypoint.sh postgres   Up      5432/tcp
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However the port is not exposed to the host. Therefore, doing the following will fail&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pgcli &lt;span class="nt"&gt;-h&lt;/span&gt; localhost &lt;span class="nt"&gt;-p&lt;/span&gt; 5432 &lt;span class="nt"&gt;-U&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DB_USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DB_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="go"&gt;could not connect to server: Connection refused
        Is the server running on host "localhost" (::1) and accepting
        TCP/IP connections on port 5432?
could not connect to server: Connection refused
        Is the server running on host "localhost" (127.0.0.1) and accepting
        TCP/IP connections on port 5432?
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this situation, we can use the IP address of the container instance to access the container without exposing the port to the host machine. The IP address can be obtained via&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker inspect docker-rails_db_1 | &lt;span class="nb"&gt;grep &lt;/span&gt;IPAddress
&lt;span class="go"&gt;            "SecondaryIPAddresses": null,
            "IPAddress": "",
                    "IPAddress": "172.24.0.4",
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the IP address above, we can access the database via&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pgcli &lt;span class="nt"&gt;-h&lt;/span&gt; 172.24.0.4 &lt;span class="nt"&gt;-p&lt;/span&gt; 5432 &lt;span class="nt"&gt;-U&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DB_USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DB_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="go"&gt;Server: PostgreSQL 11.4
Version: 2.1.1
Chat: https://gitter.im/dbcli/pgcli
&lt;/span&gt;&lt;span class="gp"&gt;Mail: https://groups.google.com/forum/#&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;forum/pgcli
&lt;span class="go"&gt;Home: http://pgcli.com
&lt;/span&gt;&lt;span class="gp"&gt;docker-rails_development&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  SchemaSpy
&lt;/h2&gt;

&lt;p&gt;For SchemaSpy, we will try will a different approach: Using the &lt;a href="https://hub.docker.com/r/schemaspy/schemaspy/" rel="noopener noreferrer"&gt;SchemaSpy's Docker container&lt;/a&gt; to connect to the existing database and generate the database diagrams for us. For this we need to know the name of the network created by docker-compose for this project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker network list | &lt;span class="nb"&gt;grep &lt;/span&gt;docker-rails
&lt;span class="go"&gt;27a3a7b42168        docker-rails_default   bridge              local
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we know the network name, we can use the &lt;code&gt;--net&lt;/code&gt; option in the &lt;code&gt;docker run&lt;/code&gt; command to connect the SchemaSpy container to the existing project network. The following will generate the database diagrams in the &lt;code&gt;db_out/&lt;/code&gt; directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DB_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;docker-rails_db_1
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DB_NET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;docker-rails_default
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; db_out
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--net&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DB_NET&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;  -v `pwd`/db_out:/output schemaspy/schemaspy:6.0.0 \
  -t pgsql \
&lt;/span&gt;&lt;span class="gp"&gt;  -db "$&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;DB_NAME&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;  -host "$&lt;/span&gt;&lt;span class="s2"&gt;{DB_HOST}"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;  -port 5432 \
&lt;/span&gt;&lt;span class="gp"&gt;  -u "$&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;DB_USER&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;  -p "$&lt;/span&gt;&lt;span class="s2"&gt;{DB_PASSWORD}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;db_out/index.html&lt;/code&gt; with a browser to checkout the generated diagrams.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5jqf17wa8huknbqdzsy3.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5jqf17wa8huknbqdzsy3.gif" alt="Using SchemaSpy" width="675" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Cleanup
&lt;/h1&gt;

&lt;p&gt;That's all for this post. The tricks mentioned above might seem limited to PostgreSQL but with little modification we believe that it can be applied on other types of databases too. As for cleanup, since it is a Docker Compose project, cleanup is as simple as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker-compose stop
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker-compose &lt;span class="nb"&gt;rm&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h4&gt;
  
  
  Found a typo?
&lt;/h4&gt;

&lt;p&gt;Thank you for reading! Found a typo? See something that could be improved or anything else that should be updated on this blog post? Thanks to &lt;a href="https://github.com/maxime1992/dev.to"&gt;this project&lt;/a&gt;, you can easily create a pull request on &lt;a href="https://github.com/shihanng/dev.to"&gt;https://github.com/shihanng/dev.to&lt;/a&gt; to propose a fix instead of posting a comment.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>postgres</category>
    </item>
  </channel>
</rss>
