<?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: Taufek Johar</title>
    <description>The latest articles on DEV Community by Taufek Johar (@taufek).</description>
    <link>https://dev.to/taufek</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%2F405108%2F9aa5bf9a-4166-4c32-a8c1-8abf282baa66.jpeg</url>
      <title>DEV Community: Taufek Johar</title>
      <link>https://dev.to/taufek</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/taufek"/>
    <language>en</language>
    <item>
      <title>Recursive Query with PostgreSQL</title>
      <dc:creator>Taufek Johar</dc:creator>
      <pubDate>Sun, 26 Jul 2020 13:30:00 +0000</pubDate>
      <link>https://dev.to/taufek/recursive-query-with-postgresql-3ikf</link>
      <guid>https://dev.to/taufek/recursive-query-with-postgresql-3ikf</guid>
      <description>&lt;p&gt;If in your database design, you have a resource with self reference&lt;br&gt;
association, you will end up having a tree like structure when you joined them&lt;br&gt;
up. In school or workplace, you might encountered tree-like chart which&lt;br&gt;
is known as Organization Chart or Org Chart for short.&lt;/p&gt;

&lt;p&gt;If I were to design the database table for this resource most probably it will&lt;br&gt;
look like below:&lt;/p&gt;
&lt;h4&gt;
  
  
  Table Name: Employees
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;column&lt;/th&gt;
&lt;th&gt;data type&lt;/th&gt;
&lt;th&gt;nullable&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;id&lt;/td&gt;
&lt;td&gt;bigint/primary key&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;full_name&lt;/td&gt;
&lt;td&gt;varchar/string&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;role&lt;/td&gt;
&lt;td&gt;varchar/string&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;manager_id&lt;/td&gt;
&lt;td&gt;bigint/foreign_key&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In Rails, my database migration will be as following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateEmployee&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;6.0&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;change&lt;/span&gt;
    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:employees&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:full_name&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:role&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;references&lt;/span&gt; &lt;span class="ss"&gt;:manager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;foreign_key: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;to_table: :employees&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And my &lt;code&gt;Employee&lt;/code&gt; model looks like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Employee&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:manager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;optional: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s1"&gt;'Employee'&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:subordinates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;foreign_key: &lt;/span&gt;&lt;span class="s1"&gt;'manager_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s1"&gt;'Employee'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's say I want to build a page that will be able to render a tree structure with a given person id. I have a Tree UI component which requires following data structure:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;full_name&lt;/th&gt;
&lt;th&gt;role&lt;/th&gt;
&lt;th&gt;manager_id&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Jack Dorsey&lt;/td&gt;
&lt;td&gt;CEO&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Michael Montano&lt;/td&gt;
&lt;td&gt;Engineering Lead&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Kayvon Beykpour&lt;/td&gt;
&lt;td&gt;Product Lead&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Joy Su&lt;/td&gt;
&lt;td&gt;VP Engineering&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Nick Turnow&lt;/td&gt;
&lt;td&gt;Platform Lead&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Keith Coleman&lt;/td&gt;
&lt;td&gt;VP Product&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Lakshmi Shankar&lt;/td&gt;
&lt;td&gt;Sr Director, Strategy &amp;amp; Operations&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Source: &lt;a href="https://theorg.com/org/twitter" rel="noopener noreferrer"&gt;https://theorg.com/org/twitter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Disclaimer: Above is just a partial of Twitter Org Chart which I pulled from above source. It's not complete and most probably not up-to-date.&lt;/p&gt;

&lt;p&gt;To build above data, I will use below builder class&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrganizationTreeBuilder&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;employee&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@employee&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;employee&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="vi"&gt;@employee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fetch_subordinates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@employee&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_subordinates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;employee&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;employee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subordinates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;subordinate&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;subordinate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fetch_subordinates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subordinate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatten&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Above builder class accepts an &lt;code&gt;Employee&lt;/code&gt; instance via the constructor and when &lt;code&gt;build&lt;/code&gt; method is called, it will recursively fetch the subordinates of the&lt;br&gt;
employee until it reaches to the level where an employee does not have any subordinates. Each time it calls &lt;code&gt;employee.subordinates&lt;/code&gt;, it will execute a sql&lt;br&gt;
query to fetch list of subordinates. Since we execute the query recursively in app level, I'm calling this as "app recursive query".&lt;/p&gt;

&lt;p&gt;Obviously this works but not really efficient if your have very large organization. Before we start to improve this, we will write unit tests for this builder class. Below is the spec file for above builder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;OrganizationTreeBuilder&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;OrganizationTreeBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ceo&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s1"&gt;'simple organization tree'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;let!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:ceo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:employee&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;let!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:dev_manager1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:employee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;manager: &lt;/span&gt;&lt;span class="n"&gt;cto&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;let!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:dev_manager2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:employee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;manager: &lt;/span&gt;&lt;span class="n"&gt;cto&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;let!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:developer1_1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:employee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;manager: &lt;/span&gt;&lt;span class="n"&gt;dev_manager1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;let!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:developer1_2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:employee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;manager: &lt;/span&gt;&lt;span class="n"&gt;dev_manager1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;let!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:developer2_1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:employee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;manager: &lt;/span&gt;&lt;span class="n"&gt;dev_manager2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;let!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:developer2_2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:employee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;manager: &lt;/span&gt;&lt;span class="n"&gt;dev_manager2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;let!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:not_relevant&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:employee&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'builds organization tree'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:aggregate_failure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;match_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="n"&gt;ceo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;dev_manager1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;dev_manager2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;developer1_1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;developer1_2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;developer2_1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;developer2_2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attributes&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Above unit test will pass when run.&lt;/p&gt;

&lt;p&gt;To gauge the performance I'm adding below test cases to above spec.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s1"&gt;'horizontal organization tree'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;let!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:ceo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:employee&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create_employee&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ceo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'builds organization tree'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;measure&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="mi"&gt;111&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;real&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s1"&gt;'vertical organization tree'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;let!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:ceo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:employee&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create_employee&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ceo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'builds organization tree'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;measure&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="mi"&gt;2047&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;real&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s1"&gt;'large organization tree'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;let!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:ceo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:employee&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create_employee&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ceo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'builds organization tree'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;measure&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="mi"&gt;3906&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;real&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_employee&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="n"&gt;create_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:employee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;manager: &lt;/span&gt;&lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;employee&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;create_employee&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;employee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are 3 different performance-related test cases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Horizontal Organization Tree. Organization tree is broad and shallow. Each employee has 10 subordinates with 2 levels.&lt;/li&gt;
&lt;li&gt;Vertical Organization Tree. Organization tree is narrow and deep. Each employee has 2 subordinates with 10 levels.&lt;/li&gt;
&lt;li&gt;Large Organization Tree. Organization tree is broad and deep. Each employee has 5 subordinates with 5 levels.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Below is how long it took to build the data for each scenario:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Horizontal Organization Tree. 0.3sec&lt;/li&gt;
&lt;li&gt;Vertical Organization Tree. 6.2sec&lt;/li&gt;
&lt;li&gt;Large Organization Tree. 11.3sec&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Time for Optimization
&lt;/h2&gt;

&lt;p&gt;PostgreSQL has build-in recursive query which is made for this. Below is updated &lt;code&gt;OrganizationTreeBuilder&lt;/code&gt; class&lt;br&gt;
with PostgreSQL recursive query.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrganizationTreeBuilder&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;employee&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@employee&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;employee&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build&lt;/span&gt;
    &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;
      WITH RECURSIVE subordinates AS (
        SELECT
         employees.id,
         employees.full_name,
         employees.role,
         employees.manager_id
        FROM
         employees
        WHERE
         employees.id = &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@employee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;

        UNION

        SELECT
         employees.id,
         employees.full_name,
         employees.role,
         employees.manager_id
        FROM
         employees
         JOIN subordinates ON subordinates.manager_id = employees.managers.id
      ) SELECT * FROM subordinates ORDER BY id ASC;
&lt;/span&gt;&lt;span class="no"&gt;    SQL&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same unit tests above will still pass since the returned data is still the same.&lt;/p&gt;

&lt;p&gt;Below are the comparison stats between previous (app recursive query) and current (PostgreSQL recursive query).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;App Recursive Query&lt;/th&gt;
&lt;th&gt;PostgreSQL Recursive Query&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Horizontal Organization Tree&lt;/td&gt;
&lt;td&gt;0.3sec&lt;/td&gt;
&lt;td&gt;0.000005sec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vertical Organization Tree&lt;/td&gt;
&lt;td&gt;6.2sec&lt;/td&gt;
&lt;td&gt;0.000005sec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Large Organization Tree&lt;/td&gt;
&lt;td&gt;11.3sec&lt;/td&gt;
&lt;td&gt;0.000005sec&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Obviously, PostgreSQL query is the winner here. Although my test dataset is small, but it does prove the point that PostgreSQL recursive query is more performant than app recursive query.&lt;/p&gt;

&lt;p&gt;But what does the query actually do? It does not make sense to me when I read it for the first time. Turns out there are 2 parts within the CTE (Common Table Expression) to make this recursive query to work. There is a non-recursive query being UNION with recursive query.&lt;/p&gt;

&lt;p&gt;Below is the non-recursive query which will be use as the base:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt;
   &lt;span class="n"&gt;employees&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;employees&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;employees&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;employees&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;manager_id&lt;/span&gt;
  &lt;span class="no"&gt;FROM&lt;/span&gt;
   &lt;span class="n"&gt;employees&lt;/span&gt; &lt;span class="n"&gt;managers&lt;/span&gt;
  &lt;span class="no"&gt;WHERE&lt;/span&gt;
   &lt;span class="n"&gt;employees&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;#{@employee.id}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From this query you will get following output:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;full_name&lt;/th&gt;
&lt;th&gt;role&lt;/th&gt;
&lt;th&gt;manager_id&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Jack Dorsey&lt;/td&gt;
&lt;td&gt;CEO&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Then comes the recursive query below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt;
   &lt;span class="n"&gt;employees&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;employees&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;employees&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;employees&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;manager_id&lt;/span&gt;
  &lt;span class="no"&gt;FROM&lt;/span&gt;
   &lt;span class="n"&gt;employees&lt;/span&gt;
   &lt;span class="no"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;subordinates&lt;/span&gt; &lt;span class="no"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;subordinates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;manager_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;managers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The recursive query joins with the CTE named &lt;code&gt;subordinates&lt;/code&gt;. It will start off with base data from the non-recursive query and it will get following output:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;full_name&lt;/th&gt;
&lt;th&gt;role&lt;/th&gt;
&lt;th&gt;manager_id&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Michael Montano&lt;/td&gt;
&lt;td&gt;Engineering Lead&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Kayvon Beykpour&lt;/td&gt;
&lt;td&gt;Product Lead&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Above are all the subordinates for employee id 1. Then it will do the same query for each of these subordinates and it will keep going until the recursive query didn't get any output. At the end it will UNION all the records you will see the end results as below:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;full_name&lt;/th&gt;
&lt;th&gt;role&lt;/th&gt;
&lt;th&gt;manager_id&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Jack Dorsey&lt;/td&gt;
&lt;td&gt;CEO&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Michael Montano&lt;/td&gt;
&lt;td&gt;Engineering Lead&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Kayvon Beykpour&lt;/td&gt;
&lt;td&gt;Product Lead&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Joy Su&lt;/td&gt;
&lt;td&gt;VP Engineering&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Nick Turnow&lt;/td&gt;
&lt;td&gt;Platform Lead&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Keith Coleman&lt;/td&gt;
&lt;td&gt;VP Product&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Lakshmi Shankar&lt;/td&gt;
&lt;td&gt;Sr Director, Strategy &amp;amp; Operations&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is a simplified usage of recursive query in PostgreSQL. There are few more things you could do which you could find out from the official &lt;a href="https://www.postgresql.org/docs/9.1/queries-with.html" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;I'm using output from above builder class to build below tree structure using d3.js library.&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%2Fblog.taufek.dev%2Fimages%2Forg_chart.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%2Fblog.taufek.dev%2Fimages%2Forg_chart.png" alt="outside_polygon"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source: &lt;a href="https://jsfiddle.net/taufek/1anLkfyg/32" rel="noopener noreferrer"&gt;https://jsfiddle.net/taufek/1anLkfyg/32&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Reference: &lt;a href="https://bl.ocks.org/d3noob/8329404" rel="noopener noreferrer"&gt;https://bl.ocks.org/d3noob/8329404&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>ElasticSearch Geo Shape</title>
      <dc:creator>Taufek Johar</dc:creator>
      <pubDate>Tue, 16 Jun 2020 16:58:00 +0000</pubDate>
      <link>https://dev.to/taufek/elasticsearch-geo-shape-592p</link>
      <guid>https://dev.to/taufek/elasticsearch-geo-shape-592p</guid>
      <description>&lt;p&gt;In previous &lt;a href="https://blog.taufek.dev/ruby/2020/06/15/elasticsearch-with-ruby.html"&gt;post&lt;/a&gt;, we looked at quick start guide on how to use ElasticSearch with Ruby.&lt;/p&gt;

&lt;p&gt;In this post, we will look at how to search based on geo location. Lets say we have food delivery app and we would like to find out the delivery surcharge amount for certain area.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create Index with Geo Shape Mapping
&lt;/h2&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Elasticsearch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

&lt;span class="n"&gt;mapping&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;surcharge: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;properties: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;delivery_area: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="s1"&gt;'geo_shape'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&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;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;index: :surcharges&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :surcharge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="p"&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;indices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put_mapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;index: :surcharges&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :surcharge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="n"&gt;mapping&lt;/span&gt;&lt;span class="p"&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;indices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;index: :surcharges&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Output&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"surcharges"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"aliases"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="s2"&gt;"mappings"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"surcharge"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"properties"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"delivery_area"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"type"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"geo_shape"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s2"&gt;"settings"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Above code, will create an index named &lt;code&gt;surcharges&lt;/code&gt; and a document named &lt;code&gt;surcharge&lt;/code&gt;. It has property of &lt;code&gt;delivery_area&lt;/code&gt; with &lt;code&gt;geo_shape&lt;/code&gt; data type.&lt;/p&gt;

&lt;h2&gt;
  
  
  Index Surcharge Document with Polygon Coordinates
&lt;/h2&gt;

&lt;p&gt;Below shows polygon area that I’ve created with Google Maps. &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4IVWS4qs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.taufek.dev/images/klcc_polygon.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4IVWS4qs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.taufek.dev/images/klcc_polygon.png" alt="klcc_polygon"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Below is the surcharge document which saying within that polygon area, the surcharge amount is $10.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;surcharge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;delivery_area: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="s1"&gt;'polygon'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;coordinates: &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;
      &lt;span class="c1"&gt;# longitude, latitude&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;101.710997&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.157035&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;101.710997&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.152954&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;101.717316&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.152954&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;101.717316&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.157035&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;101.710997&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.157035&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;]]&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="mf"&gt;10.0&lt;/span&gt;
&lt;span class="p"&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;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;surcharge&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="ss"&gt;index: :surcharges&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;type: :surcharge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="n"&gt;surcharge&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Output&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"_index"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"surcharges"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"_type"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"surcharge"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"_id"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"_version"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"result"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"created"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"_shards"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"total"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"successful"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"failed"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="s2"&gt;"_seq_no"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"_primary_term"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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



&lt;h2&gt;
  
  
  Search with Geo Point
&lt;/h2&gt;

&lt;p&gt;Once I have my surcharge data indexed, I can start querying based on a coordinate. If my drop off point in somewhere inside the polygon, I’m expecting the query will return the surcharge document. &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ehhIselS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.taufek.dev/images/inside_polygon.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ehhIselS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.taufek.dev/images/inside_polygon.png" alt="inside_polygon"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;query: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;bool: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;filter: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;geo_shape: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="s1"&gt;'delivery_area'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="ss"&gt;shape: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="ss"&gt;type: :point&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="ss"&gt;coordinates: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
                  &lt;span class="mf"&gt;101.714997&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;#longitude&lt;/span&gt;
                  &lt;span class="mf"&gt;3.155647&lt;/span&gt; &lt;span class="c1"&gt;#latitude&lt;/span&gt;
                &lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&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;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;index: &lt;/span&gt;&lt;span class="s1"&gt;'surcharges'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Output&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"took"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"timed_out"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"_shards"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"total"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"successful"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"skipped"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"failed"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
 &lt;span class="s2"&gt;"hits"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="s2"&gt;"total"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"max_score"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"hits"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
     &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="s2"&gt;"_index"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"surcharges"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="s2"&gt;"_type"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"surcharge"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="s2"&gt;"_id"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="s2"&gt;"_score"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="s2"&gt;"_source"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"delivery_area"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"type"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"polygon"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="s2"&gt;"coordinates"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;[[[&lt;/span&gt;&lt;span class="mf"&gt;101.710997&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.157035&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;101.710997&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.152954&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;101.717316&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.152954&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;101.717316&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.157035&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;101.710997&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.157035&lt;/span&gt;&lt;span class="p"&gt;]]]&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
         &lt;span class="s2"&gt;"amount"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;10.0&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;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;Then if my drop off point in somewhere outside the polygon, I’m expecting the query will not return any surcharge document. &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--N1lSUqyC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.taufek.dev/images/outside_polygon.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--N1lSUqyC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.taufek.dev/images/outside_polygon.png" alt="outside_polygon"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;query: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;bool: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;filter: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;geo_shape: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="s1"&gt;'delivery_area'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="ss"&gt;shape: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="ss"&gt;type: :point&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="ss"&gt;coordinates: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="mf"&gt;101.7077343&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;#longitude&lt;/span&gt;
                &lt;span class="mf"&gt;3.157032&lt;/span&gt; &lt;span class="c1"&gt;#latitude&lt;/span&gt;
              &lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&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;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;index: &lt;/span&gt;&lt;span class="s1"&gt;'surcharges'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Output&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"took"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"timed_out"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"_shards"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"total"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"successful"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"skipped"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"failed"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="s2"&gt;"hits"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"total"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"max_score"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"hits"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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



&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;Geo shape is useful when you have data associated with coordinates of land area. Once you indexed those as a polygon in ES index, you could use Geo Shape query to fetch documents that intersect with a particular geo point. There are many other useful features too such as you could set a polygon with a hole in the middle or you could search based on radius instead of point. For more info you can head to ES official documents (&lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-shape.html"&gt;geo-shape-datatype&lt;/a&gt; and &lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-geo-shape-query.html"&gt;geo-shape-query&lt;/a&gt;).&lt;/p&gt;

</description>
      <category>elasticsearch</category>
      <category>ruby</category>
    </item>
    <item>
      <title>Learn ElasticSearch with Ruby</title>
      <dc:creator>Taufek Johar</dc:creator>
      <pubDate>Mon, 15 Jun 2020 16:00:00 +0000</pubDate>
      <link>https://dev.to/taufek/learn-elasticsearch-with-ruby-1d4e</link>
      <guid>https://dev.to/taufek/learn-elasticsearch-with-ruby-1d4e</guid>
      <description>&lt;p&gt;ElasticSearch (ES) is a popular search and analytics engine. I’ve been using it in most of my Ruby on Rails projects. Usually in a project we will started of by building an ActiveRecord model and establish the ES index mapping before we could start running the search.&lt;/p&gt;

&lt;p&gt;In this post, I’ll start with basic and minimal code as possible to get us started with ES.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-requisite
&lt;/h2&gt;

&lt;p&gt;Install &lt;a href="https://github.com/elastic/elasticsearch-ruby"&gt;elasticsearch&lt;/a&gt; gem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initialize ES Client
&lt;/h2&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Elasticsearch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We’ll be using above &lt;code&gt;client&lt;/code&gt; instance throughout our example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create ES Index Mapping
&lt;/h2&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;setting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;properties: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;type: :text&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="ss"&gt;birth_date: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;type: :date&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;index: :foo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="p"&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;indices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put_mapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;index: :foo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="n"&gt;mapping&lt;/span&gt;&lt;span class="p"&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;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;index: :foo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="n"&gt;setting&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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



&lt;p&gt;This creates new index named &lt;code&gt;foo&lt;/code&gt; with following properties:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;name&lt;/code&gt; field as &lt;code&gt;text&lt;/code&gt; data type.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;birth_date&lt;/code&gt; field as &lt;code&gt;date&lt;/code&gt; data type.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Index Document(s)
&lt;/h2&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;adam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'Adam Hakeem'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;birth_date: &lt;/span&gt;&lt;span class="s1"&gt;'2006-12-08'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;alif&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'Alif Hussain'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;birth_date: &lt;/span&gt;&lt;span class="s1"&gt;'2009-04-28'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;ammar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'Ammar Hamzah'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;birth_date: &lt;/span&gt;&lt;span class="s1"&gt;'2016-07-03'&lt;/span&gt;
&lt;span class="p"&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;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;index: :foo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="n"&gt;adam&lt;/span&gt;&lt;span class="p"&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;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;index: :foo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="n"&gt;alif&lt;/span&gt;&lt;span class="p"&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;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;index: :foo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="n"&gt;ammar&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Search away
&lt;/h2&gt;

&lt;p&gt;Now you could start searching your data in ES index.&lt;/p&gt;

&lt;p&gt;Below search by name with partial keyword &lt;code&gt;am*&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;query: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;query_string: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;query: &lt;/span&gt;&lt;span class="s1"&gt;'am*'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;index: :foo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;#Output:&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"took"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"timed_out"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"_shards"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"total"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"successful"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"skipped"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"failed"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="s2"&gt;"hits"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"total"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"max_score"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;0.2876821&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"hits"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"_index"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"_type"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"doc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"_id"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"_score"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;0.2876821&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"_source"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"Ammar Hamzah"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"birth_date"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"2016-07-03"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Below search by date range.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;query: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;range: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;birth_date: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;gte: &lt;/span&gt;&lt;span class="s1"&gt;'2009-01-01'&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&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;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;index: :foo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;#Output:&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"took"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"timed_out"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"_shards"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"total"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"successful"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"skipped"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"failed"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="s2"&gt;"hits"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"total"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"max_score"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"hits"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"_index"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"_type"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"doc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"_id"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"_score"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"_source"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"Alif Hussain"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"birth_date"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"2009-04-28"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"_index"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"_type"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"doc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"_id"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"_score"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"_source"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"Ammar Hamzah"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"birth_date"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"2016-07-03"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Cleanup After Yourself
&lt;/h2&gt;

&lt;p&gt;If you are done playing around with your index, you might want to remove your dummy data. You can run below to delete our particular dummy index.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;index: :foo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;Hopefully this will give you a good start on how to learn ElasticSearch with Ruby.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Host Jekyll Site in AWS S3</title>
      <dc:creator>Taufek Johar</dc:creator>
      <pubDate>Mon, 15 Jun 2020 05:40:00 +0000</pubDate>
      <link>https://dev.to/taufek/host-jekyll-site-in-aws-s3-49ec</link>
      <guid>https://dev.to/taufek/host-jekyll-site-in-aws-s3-49ec</guid>
      <description>&lt;p&gt;In previous &lt;a href="https://blog.taufek.dev/kubernetes/2020/06/14/deploy-jekyll-site-to-heroku.html"&gt;post&lt;/a&gt;, I’ve wrote about on how to push a Jekyll site to Heroku. In this post it will be about how I host this blog in AWS S3 bucket.&lt;/p&gt;

&lt;p&gt;Turns out it is much simpler to do it in S3.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a S3 bucket
&lt;/h2&gt;

&lt;p&gt;Create a S3 bucket and enable Web Hosting in the properties.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure Github Action
&lt;/h2&gt;

&lt;p&gt;You will need to configure following secrets in your Github setting.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;AWS_S3_BUCKET&lt;/code&gt; with your bucket name.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; with your AWS access key id.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt; with your AWS secret access id.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Sync to S3

on:
push:
branches:
    - master

        jobs:
        deploy:
        runs-on: ubuntu-latest
        steps:
    - uses: actions/checkout@v1

    - name: Retrieve Gem Bundles Cache
        uses: actions/cache@v2
        with:
        path: _bundle
        key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
        restore-keys: |
        ${{ runner.os }}-gems-

    - name: Generate site
        run: |
        mkdir -p _bundle/cache
        chmod a+rwx -R .
        docker-compose run -e JEKYLL_ENV=production jekyll bundle install
        docker-compose run -e JEKYLL_ENV=production jekyll bundle exec jekyll build --trace

    - uses: jakejarvis/s3-sync-action@master
        with:
        args: --acl public-read --follow-symlinks --delete
        env:
        AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }}
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        AWS_REGION: 'ap-southeast-1'
        SOURCE_DIR: '_site'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;With a push to &lt;code&gt;master&lt;/code&gt; branch, it will trigger a build which will&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Checkout code from &lt;code&gt;master&lt;/code&gt; branch.&lt;/li&gt;
&lt;li&gt;Generate Jekyll static files.&lt;/li&gt;
&lt;li&gt;Push generated files inside &lt;code&gt;_site&lt;/code&gt; folder to your S3 bucket.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;This seems like a cheaper option for hosting a static site and have SSL configured in CloudFront compared to paying for a dyno in Heroku to host a site with SSL. For Heroku, I will need to pay atleast $7/month for a dyno and for CloudFront it will based on usage. Since my blog does not have much visitors, my AWS bill is very small which is around $1/month. This includes S3, Route 53 and CloudFront services.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Deploy Jekyll Site to Heroku</title>
      <dc:creator>Taufek Johar</dc:creator>
      <pubDate>Sun, 14 Jun 2020 07:30:00 +0000</pubDate>
      <link>https://dev.to/taufek/deploy-jekyll-site-to-heroku-4lk8</link>
      <guid>https://dev.to/taufek/deploy-jekyll-site-to-heroku-4lk8</guid>
      <description>&lt;p&gt;Generally there are 2 ways to deploy Jekyll static site to Heroku.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deploy as rails app using web server gem such as &lt;code&gt;puma&lt;/code&gt;, &lt;code&gt;passenger&lt;/code&gt;, &lt;code&gt;thin&lt;/code&gt;, ‘etc’.&lt;/li&gt;
&lt;li&gt;Deploy as Docker image running &lt;code&gt;nginx&lt;/code&gt; service.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this post, I chose 2nd approach since I like to use Docker image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prepare Dockerfile
&lt;/h2&gt;

&lt;p&gt;We will serve this Jekyll site via nginx service. In order to map Heroku portnumber to our nginx service, we will need to use below &lt;code&gt;default.conf&lt;/code&gt; template.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server {
  # Placeholder for Heroku port
  listen $PORT;

  location / {
    root /usr/share/nginx/html;
    index index.html;
  }
}

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



&lt;p&gt;Dockerfile.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM nginx:alpine

# copy nginx `default.conf`
COPY nginx/default.conf /etc/nginx/conf.d/
# copy Jekyll generated files
COPY _site /usr/share/nginx/html/

# replace $PORT placeholder with HEROKU given port in default.conf and run nginx service
CMD sed -i -e 's/$PORT/'"$PORT"'/g' /etc/nginx/conf.d/default.conf &amp;amp;&amp;amp; nginx -g 'daemon off;'

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



&lt;p&gt;You can named this file as &lt;code&gt;Dockerfile&lt;/code&gt; or have a suffix to indiciate theHeroku process. For instance, if you want to run this application as &lt;code&gt;web&lt;/code&gt;process in Heroku, you could name it as &lt;code&gt;Dockerfile.web&lt;/code&gt;. This works well ifyou have multiple &lt;code&gt;Dockerfile&lt;/code&gt;s with different process such as &lt;code&gt;web&lt;/code&gt;, &lt;code&gt;worker&lt;/code&gt;, or&lt;code&gt;clock&lt;/code&gt;. Later on, when you run &lt;code&gt;heroku container:push --recursive&lt;/code&gt;, it willbuild all the &lt;code&gt;Dockerfile&lt;/code&gt;s with the correct suffix and push them to Heroku dockerregistry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure Github Action
&lt;/h2&gt;

&lt;p&gt;Even without Github Action, all I need is Heroku CLI to deploy this Dockerimage using below commands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; bundle exec jekyll build

&amp;gt; heroku container:login

&amp;gt; heroku container:push -a &amp;lt;HEROKU_APP_NAME&amp;gt; --resursive

&amp;gt; heroku container:release -a &amp;lt;HEROKU_APP_NAME&amp;gt; web

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



&lt;p&gt;Basically, in Github Actions, we are running above commands. Below is the fullGithub Action workflow yaml.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Push Container to Heroku

on:
  push:
    branches:
      - master

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v1

    - name: Retrieve Gem Bundles Cache
      uses: actions/cache@v2
      with:
        path: _bundle
        key: $-gems-$
        restore-keys: |
          $-gems-

    - name: Generate site
      run: |
        mkdir -p _bundle/cache
        chmod a+rwx -R .
        docker-compose run -e JEKYLL_ENV=production jekyll bundle install
        docker-compose run -e JEKYLL_ENV=production jekyll bundle exec jekyll build --trace

    - name: Login to Heroku Container registry
      env:
        HEROKU_API_KEY: $
      run: heroku container:login

    - name: Build and push
      env:
        HEROKU_API_KEY: $
      run: heroku container:push -a $ --recursive

    - name: Release
      env:
        HEROKU_API_KEY: $
      run: heroku container:release -a $ web

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



&lt;p&gt;With a push to &lt;code&gt;master&lt;/code&gt; branch, it will trigger a build which will&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Checkout code from &lt;code&gt;master&lt;/code&gt; branch.&lt;/li&gt;
&lt;li&gt;Generate Jekyll static files.&lt;/li&gt;
&lt;li&gt;Build and push the Docker image to heroku registry.&lt;/li&gt;
&lt;li&gt;Deploy the Docker image to Heroku with &lt;code&gt;web&lt;/code&gt; process.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;This approach not only work for static site, but also for other typesof application such as Rails application. Having a Dockerfile will give youfull control on what other linux packages you wish to install within theapplication server instance such as ImageMagick. It will also give youconsistent environment between running your application on your local machineand on Heroku.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Let Cert-Manager Manage LetsEncrypt Certificates</title>
      <dc:creator>Taufek Johar</dc:creator>
      <pubDate>Mon, 08 Jun 2020 16:20:00 +0000</pubDate>
      <link>https://dev.to/taufek/let-cert-manager-manages-letsencrypt-certificates-3pl4</link>
      <guid>https://dev.to/taufek/let-cert-manager-manages-letsencrypt-certificates-3pl4</guid>
      <description>&lt;p&gt;Since this blog is using &lt;code&gt;*.dev&lt;/code&gt; domain, I will need to configure SSL certificate in order to serve this content in HTTPS. I’m using &lt;a href="https://letsencrypt.org/"&gt;LetsEncrypt&lt;/a&gt; to issue my certificate and the certificate only valid for 3 months. I will need to renew them periodically. I can either do that manually when the certificate is almost expire or I can automate the process.&lt;/p&gt;

&lt;p&gt;In this post, we will look into how to automate the certificate renewal with &lt;a href="https://cert-manager.io/docs/"&gt;cert-manager&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I’ve been using &lt;a href="https://github.com/kubernetes-sigs/aws-alb-ingress-controller"&gt;aws-alb-ingress-controller&lt;/a&gt; as Ingress implementation in my cluster and but for this, I will need to swap to another Ingress implementation which is &lt;a href="https://github.com/kubernetes-sigs/aws-alb-ingress-controller"&gt;nginx-ingress&lt;/a&gt;. This is because currently, there is no way for the &lt;a href="https://cert-manager.io/docs/"&gt;cert-manager&lt;/a&gt; to upload the certificate to the ALB.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-requisite
&lt;/h2&gt;

&lt;p&gt;Add below IAM policy to your “node” role.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Version": "2012-10-17",
"Statement": [
  {
    "Effect": "Allow",
    "Action": "route53:GetChange",
    "Resource": "arn:aws:route53:::change/*"
  },
  {
    "Effect": "Allow",
    "Action": [
      "route53:ChangeResourceRecordSets",
      "route53:ListResourceRecordSets"
    ],
    "Resource": "arn:aws:route53:::hostedzone/*"
  },
  {
    "Effect": "Allow",
    "Action": "route53:ListHostedZonesByName",
    "Resource": "*"
  }
]

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



&lt;h2&gt;
  
  
  Install Cert Manager
&lt;/h2&gt;

&lt;p&gt;Run below to install &lt;a href="https://cert-manager.io/docs/"&gt;cert-manager&lt;/a&gt; and its dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v0.15.1/cert-manager.yaml

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



&lt;p&gt;Apply below &lt;code&gt;Issuer&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: cert-manager.io/v1alpha2
kind: Issuer
metadata:
  name: letsencrypt-prod
  namespace: default
spec:
  acme:
  server: https://acme-v02.api.letsencrypt.org/directory
  email: taufek@gmail.com

  privateKeySecretRef:
    name: letsencrypt-prod

  solvers:
      - selector:
      dnsNames:
          - blog.taufek.dev
          http01:
            ingress:
              class: nginx
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In this &lt;code&gt;Issuer&lt;/code&gt;, we configure to use:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://letsencrypt.org/"&gt;LetsEncrypt&lt;/a&gt; production api.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;http01&lt;/code&gt; challenge to verify that we own the domain. The other method is &lt;code&gt;dns01&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nginx&lt;/code&gt; ingress implementation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And lastly, we apply below &lt;code&gt;Ingress&lt;/code&gt; rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress
  namespace: default
  annotations:
    kubernetes.io/ingress.class: 'nginx'
    cert-manager.io/issuer: 'letsencrypt-prod'
  spec:
    tls:
      - hosts:
        - blog.taufek.dev
        secretName: blog-taufek-dev-tls
    rules:
      - host: blog.taufek.dev
          http:
          paths:
           - path: /
               backend:
                 serviceName: blog-nodeport-service
                 servicePort: 80
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Wait for a minute for &lt;a href="https://cert-manager.io/docs/"&gt;cert-manager&lt;/a&gt; to do its thing. It will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Send a CertificationRequest to &lt;a href="https://letsencrypt.org/"&gt;LetsEncrypt&lt;/a&gt; API.&lt;/li&gt;
&lt;li&gt;Setup and API to fulfill &lt;a href="https://letsencrypt.org/"&gt;LetsEncrypt&lt;/a&gt; challenge.&lt;/li&gt;
&lt;li&gt;Generate the certificate files.&lt;/li&gt;
&lt;li&gt;Apply the new/renewed certificate to &lt;a href="https://github.com/kubernetes-sigs/aws-alb-ingress-controller"&gt;nginx-ingress&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can run below to check the certificate status&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; kubectl describe certificate blog-taufek-dev

Name: blog-taufek-dev-tls
...
API Version: cert-manager.io/v1alpha3
Kind: Certificate
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Requested 5m20s cert-manager Created new CertificateRequest resource "blog-taufek-dev-tls-918422041"
Normal Issued 4m44s cert-manager Certificate issued successfully

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



&lt;p&gt;And that’s it. I have successfully setup &lt;a href="https://letsencrypt.org/"&gt;LetsEncrypt&lt;/a&gt; certificate for my blog it it will be auto renew by &lt;a href="https://cert-manager.io/docs/"&gt;cert-manager&lt;/a&gt; when the time comes.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://cert-manager.io/docs/"&gt;cert-manager&lt;/a&gt; also comes with &lt;a href="https://cert-manager.io/docs/usage/kubectl-plugin/"&gt;kubectl-plugin&lt;/a&gt;. When installed, there are few additional commands I can use via &lt;code&gt;kubectl&lt;/code&gt; related to certificate management.&lt;/p&gt;

&lt;p&gt;For example, I can run below to renew all my certificates&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl cert-manager renew --all

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



&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;Certificate renewal is a must for production grade application. You wouldn’t want to miss renewing your applications certificate when it almost expires. The only thing that made me not to use this implementation is because it requires Classic ELB and I’m still evaluating if the cost is still within my budget. If I were to setup for a profitable application, this is definitely my choice of implementation.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Fortify Kubernetes Cluster with Bastion</title>
      <dc:creator>Taufek Johar</dc:creator>
      <pubDate>Fri, 05 Jun 2020 19:00:00 +0000</pubDate>
      <link>https://dev.to/taufek/fortify-kubernetes-cluster-with-bastion-1ol7</link>
      <guid>https://dev.to/taufek/fortify-kubernetes-cluster-with-bastion-1ol7</guid>
      <description>&lt;p&gt;In previous &lt;a href="//blog.taufek.dev/kubernetes/2020/06/03/manage-services-with-ingress.html"&gt;post&lt;/a&gt; we looked into Ingress as the front facing entry point for your applications users. In this post, we will look into Bastion as the backdoor entry point for infrastructure administration users.&lt;/p&gt;

&lt;p&gt;Currently, all the EC2 instances in my cluster are on public subnet which all of them has public IP address. In order to secure the cluster further, we can move our cluster to a private subnet and setup a Bastion instance as a ‘jump’ server. Bastion will be the only instance on your public subnet and it has access to the cluster. We can easily achieve this setup with &lt;code&gt;kops&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create New Cluster with Bastion
&lt;/h2&gt;

&lt;p&gt;For creating new cluster, you can run below command and your will get a cluster on private network with a Bastion instance ready:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kops create cluster \
  --name=k8s.blog.taufek.dev \
  --state=s3://k8s.blog.taufek.dev.state \
  --zones=ap-southeast-1a,ap-southeast-1b,ap-southeast-1c \
  --dns-zone=k8s.blog.taufek.dev \
  --topology=private \
  --bastion
  --yes
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then run below commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kops update cluster \
  --name kubernetes.taufek.dev \
  --yes \
  --state=s3://k8s.blog.taufek.dev.state

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



&lt;p&gt;In a minute or so you will have your cluster ready. You could run below command to know the status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kops validate cluster --state=s3://k8s.blog.taufek.dev.state

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



&lt;h2&gt;
  
  
  Add Bastion to Existing Cluster
&lt;/h2&gt;

&lt;p&gt;For existing cluster, you will need to to edit your cluster object. Run below command to open up your cluster yaml file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kops edit cluster --state=s3://k8s.blog.taufek.dev.state

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



&lt;p&gt;Edit your cluster yml with following settings. You could get the subnets by running &lt;code&gt;kops&lt;/code&gt; create command with dry-run flag and output to a file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  ...
  subnets:
  - cidr: ...
    name: ap-southeast-1a
    type: Private # change this to Private
    zone: ap-southeast-1a
  - cidr: ...
    name: ap-southeast-1b
    type: Private # change this to Private
    zone: ap-southeast-1b
  - cidr: ...
    name: ap-southeast-1c
    type: Private # change this to Private
    zone: ap-southeast-1c
  - cidr: ... # add these new Utility subnets
    name: utility-ap-southeast-1a
    type: Utility
    zone: ap-southeast-1a
  - cidr: ...
    name: utility-ap-southeast-1b
    type: Utility
    zone: ap-southeast-1b
  - cidr: ...
    name: utility-ap-southeast-1c
    type: Utility
    zone: ap-southeast-1c
  topology:
    bastion: # add bastion entry here
      bastionPublicName: bastion.blog.k8s.taufek.dev
    dns:
      type: Public
    masters: private # change to private
    nodes: private # change to private
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then run below to create Bastion instance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kops create instancegroup bastions \
  --role Bastion \
  --subnet ap-southeast-1a,ap-southeast-1b,ap-southeast-1c \
  --state=s3://k8s.blog.taufek.dev.state

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



&lt;p&gt;Run below command to push update to your cluster&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kops update cluster --yes --state=s3://k8s.blog.taufek.dev.state
kops rolling-update cluster --state=s3://k8s.blog.taufek.dev.state --yes

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



&lt;p&gt;That’s all too it, and just wait for few minute for your cluster to be reconfigured with private subnet. And you should also be able to ssh into your Bastion instance with following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh -A admin@bastion.blog.taufek.dev

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



&lt;p&gt;Below is how the cluster looks like before, without Bastion:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UginvXog--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.taufek.dev/images/cluster_without_bastion.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UginvXog--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.taufek.dev/images/cluster_without_bastion.png" alt="Cluster without Bastion"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And below is how it looks like now, with Bastion:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wGkKQZ8S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.taufek.dev/images/cluster_with_bastion.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wGkKQZ8S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.taufek.dev/images/cluster_with_bastion.png" alt="Cluster with Bastion"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One thing that made me hesitated to stick with this setup, is the additional ELBs it created for Bastion and Master instances. This means it will be a significant cost increase in my AWS bill. So I decided to undo this change.&lt;/p&gt;

&lt;p&gt;In order to remove Bastion you will need to edit back your cluster yaml file and remove Bastion instance.&lt;/p&gt;

&lt;p&gt;Edit your cluster yml with following settings&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  ...
  subnets:
  - cidr: ...
    name: ap-southeast-1a
    type: Public # change this back to Public
    zone: ap-southeast-1a
  - cidr: ...
    name: ap-southeast-1b
    type: Public # change this back to Public
    zone: ap-southeast-1b
  - cidr: ...
    name: ap-southeast-1c
    type: Public # change this back to Public
    zone: ap-southeast-1c
  ... # remove the Utility subnets
  topology:
  ... # remove bastion
    dns:
      type: Public
    masters: public # change this back to public
    nodes: public # change this back to public
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Run below command to push update to your cluster&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kops update cluster --yes --state=s3://k8s.blog.taufek.dev.state
kops rolling-update cluster --state=s3://k8s.blog.taufek.dev.state --yes

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



&lt;p&gt;Run below command to remove Bastion instance&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kops delete instancegroup bastions --yes --state=s3://k8s.blog.taufek.dev.state

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



&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;Bastion is not a concept solely for Kubernetes but it’s a common practice in fortifying your software infrastructure even without Kubernetes. I found that by looking at all the AWS services created by &lt;code&gt;kops&lt;/code&gt;, you could learn so much about best practices in software infrastructure. I don’t have much experience in setting up production environment but I learn a lot in past couple of weeks by looking at how &lt;code&gt;kops&lt;/code&gt; wires everything together.&lt;/p&gt;

&lt;p&gt;You can’t appreciate enough the complexity of services &lt;code&gt;kops&lt;/code&gt; utility created under the hood with just one command, &lt;code&gt;kops create ...&lt;/code&gt;. I can’t imagine the hours you have to put in to setup everything manually versus doing it with &lt;code&gt;kops&lt;/code&gt;. And it is just not about creating but also managing the infrastructure with this utility. Not only you save your time but rest assured that your infrastructure is configured in a correct manner.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Manage Applications with Ingress</title>
      <dc:creator>Taufek Johar</dc:creator>
      <pubDate>Wed, 03 Jun 2020 15:00:00 +0000</pubDate>
      <link>https://dev.to/taufek/manage-applications-with-ingress-omg</link>
      <guid>https://dev.to/taufek/manage-applications-with-ingress-omg</guid>
      <description>&lt;p&gt;Ingress is the gate keeper to your cluster. All external access will go through your Ingress Controller and depending on the rules that you set, it will be redirected to a particular service.&lt;/p&gt;

&lt;p&gt;There are multiple benefits of having Ingress in my cluster.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Replaced Classic ELB with Application ELB.&lt;/li&gt;
&lt;li&gt;Rules and External DNS.&lt;/li&gt;
&lt;li&gt;Redirect to HTTPS.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are few implementations of Kubernetes Ingress we can choose from and the one I’m using is &lt;a href="https://github.com/kubernetes-sigs/aws-alb-ingress-controller"&gt;aws-alb-ingress-controller&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Replaced Classic ELB with Application ELB
&lt;/h2&gt;

&lt;p&gt;Below is my previous setup before having Ingress.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xe6wANVr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.taufek.dev/images/cluster_without_ingress.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xe6wANVr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.taufek.dev/images/cluster_without_ingress.png" alt="Cluster without Ingress"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you visited this blog, the request goes through Kubernetes ‘LoadBalancer’ Service and it will be redirected to one of the Pod which will serve this lovely content 😛. For the purpose of this simple site, this is more than sufficient.&lt;/p&gt;

&lt;p&gt;But I’m not going to stop here since the purpose of the whole thing is to learn and we will move forward with Ingress.&lt;/p&gt;

&lt;p&gt;Below is my current setup with Ingress.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ONNpHjll--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.taufek.dev/images/cluster_with_ingress.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ONNpHjll--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.taufek.dev/images/cluster_with_ingress.png" alt="Cluster with Ingress"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now the previous Classic ELB has been replaced by Application ELB. ELB acts as an Ingress Proxy. It will be the single point of entry for all external access to my cluster. The Kubernetes ‘LoadBalancer’ Service is now replaced by Kubernetes ‘NodePort’ Services which are only accessible internally.&lt;/p&gt;

&lt;p&gt;One benefit of having Application ELB over Classic ELB, the pricing is much cheaper 💰. Yeay!. And the goodness does not stop here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rules and External DNS
&lt;/h2&gt;

&lt;p&gt;Ingress Rules contains the instructions that will help Ingress Controller to redirect the request to the correct services.&lt;/p&gt;

&lt;p&gt;Below is an excerpt of my Ingress Rules. It says all request that matches the &lt;code&gt;host&lt;/code&gt; and &lt;code&gt;path&lt;/code&gt;,should go to a particular Kubernetes ‘NodePort’ Service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress
  ...
spec:
  rules:
    - host: blog.taufek.dev
      http:
        paths:
         - path: /*
           backend:
             serviceName: blog-nodeport-service
             servicePort: 80

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



&lt;p&gt;Another cool thing about this is the External DNS which will automatically sync with Hosted Zone in AWS Route 56. If I need to add a new rule with new ‘host’, like below, this will trigger a task to update my Hosted Zone with new Record Set. Or if I remove a particular ‘host’ from the rules, it will also remove the Record Set.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spec:
  rules:
    - host: blog.taufek.dev
      http:
        paths:
         - path: /*
           backend:
             serviceName: blog-nodeport-service
             servicePort: 80
    - host: api.taufek.dev
      http:
        paths:
         - path: /*
           backend:
             serviceName: api-nodeport-service
             servicePort: 8080

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



&lt;h2&gt;
  
  
  Redirect to HTTPS
&lt;/h2&gt;

&lt;p&gt;Not all browsers like Chrome will auto redirect my &lt;code&gt;*.dev&lt;/code&gt; domain to https. I would like to have consistent experience for all browsers so I will force all the request to HTTPS. This is easily achieved with Ingress Rules. Below is the configuration to implement HTTPS redirect.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/certificate-arn: &amp;lt;ADD_YOUR_CERT_ARN_HERE&amp;gt;
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'
    alb.ingress.kubernetes.io/actions.ssl-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}'
spec:
  rules:
    - host: blog.taufek.dev
      http:
        paths:
         - path: /*
           backend:
             serviceName: ssl-redirect
             servicePort: use-annotation
         - path: /*
           backend:
             serviceName: blog-nodeport-service
             servicePort: 80

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



&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;With this change, not only it helps me to cut down my AWS bill, it also made the cluster to be easily managed. If I need to serve new application, I no longer need to spawn new LoadBalancer Service, configure the SSL cert and etc. I only need to do it once for the Application ELB that serves as Ingress Proxy. All new applications will be managed via Ingress Rules. Although it looks more complex on the diagram but it helps if you plan to have multiple applications in your cluster.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>aws</category>
    </item>
    <item>
      <title>Reduce Kubernetes Cluster Costs on AWS</title>
      <dc:creator>Taufek Johar</dc:creator>
      <pubDate>Mon, 01 Jun 2020 10:31:00 +0000</pubDate>
      <link>https://dev.to/taufek/reduce-kubernetes-cluster-costs-on-aws-7b1</link>
      <guid>https://dev.to/taufek/reduce-kubernetes-cluster-costs-on-aws-7b1</guid>
      <description>&lt;p&gt;When you spun up a Kubernetes cluster for your own personal blog, you are on a tight budget. Kubernetes cluster can blow a hole in your pocket if you don’t pay attention to the AWS services it uses under the hood.&lt;/p&gt;

&lt;p&gt;For this blog, the Kubernetes cluster was provisioned using &lt;a href="https://github.com/kubernetes/kops"&gt;kops&lt;/a&gt;. There are settings you could change only during creation and there are also things you could change post creation. For the former point, you better plan it early before you create the cluster.&lt;/p&gt;

&lt;p&gt;Mainly there are 2 things that you need to pay attention for costs reduction.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;EC2 instances.&lt;/li&gt;
&lt;li&gt;EBS volumes.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  EC2 Instances
&lt;/h2&gt;

&lt;p&gt;The minimum number of instances you need to run a proper cluster is 3 instances,1 master and 2 nodes. By default, the EC2 instance will be an on-demand instance so to reduce the cost, you need to change to AWS Spot instances. For this, you have to explicitly set the maximum price for your master and nodes instances. This settings can be done either during creation or post creation.&lt;/p&gt;

&lt;h3&gt;
  
  
  New Cluster
&lt;/h3&gt;

&lt;p&gt;If you want to set it during cluster creation, you will need to run few commands.&lt;/p&gt;

&lt;p&gt;First you will need to generate the cluster yaml file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kops create cluster \
  --name=k8s.blog.taufek.dev \
  --state=s3://k8s.blog.taufek.dev.state \
  --zones=ap-southeast-1a,ap-southeast-1b,ap-southeast-1c \
  --dns-zone=k8s.blog.taufek.dev \
  --dry-run \
  --output yaml | tee k8s.blog.taufek.dev.yml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then edit the &lt;code&gt;k8s.blog.taufek.dev.yml&lt;/code&gt; file by adding &lt;code&gt;maxPrice: "#.##"&lt;/code&gt; for both master and nodes settings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: kops.k8s.io/v1alpha2
kind: InstanceGroup
metadata:
  ...
  name: master-ap-southeast-1a
spec:
  ...
  maxPrice: "0.10" # Add this line
  maxSize: 1
  ...
---
apiVersion: kops.k8s.io/v1alpha2
kind: InstanceGroup
metadata:
  ...
  name: nodes
spec:
  ...
  maxPrice: "0.10" # Add this line
  maxSize: 2
  ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then run below command to create the cluster&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kops create -f k8s.blog.taufek.dev.yml --state=s3://k8s.blog.taufek.dev.state
kops create secret --name k8s.blog.taufek.dev ssh publickey admin -i ~/.ssh/id_rsa.pub
kops update cluster k8s.taufek.dev --yes --state=s3://k8s.blog.taufek.dev.state
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Note: Before running above to create the cluster, you might want to read &lt;code&gt;EBS Volumes&lt;/code&gt; section below because part of the setting is not easily change after the cluster is created.&lt;/p&gt;

&lt;h3&gt;
  
  
  Existing Cluster
&lt;/h3&gt;

&lt;p&gt;If you already have a running cluster, you can edit the cluster settings. First, run below command to open up the Cluster object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kops edit cluster --state=s3://k8s.blog.taufek.dev.state
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then it will open up similar cluster yaml file in creation step above. Add the &lt;code&gt;maxPrice&lt;/code&gt; fields and save the file.&lt;/p&gt;

&lt;p&gt;Then run below to push the change to your cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kops update cluster --yes --state=s3://k8s.blog.taufek.dev.state
kops rolling-update cluster --yes --state=s3://k8s.blog.taufek.dev.state
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Run below to know when your cluster is ready&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kops validate cluster --state=s3://k8s.blog.taufek.dev.state
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  EBS Volumes
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;kops&lt;/code&gt; provisions 1 volume for each instances in your cluster and 2 volumes for etcd cluster. In my 1 master and 2 nodes cluster, I will be given 5 volumes.By default, the volumes size are crazily big, at least too big for running my blog site. By default, master volume size is 64G, nodes is 128G and etcd is 2x20GB volumes.&lt;/p&gt;

&lt;p&gt;For master and nodes volume size, they can be change during creation and post creation. Unfortunately, &lt;code&gt;kops&lt;/code&gt;, does not allow to change etcd volume size during creation.&lt;/p&gt;

&lt;h3&gt;
  
  
  New Cluster
&lt;/h3&gt;

&lt;p&gt;First you will need to generate the cluster yaml file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kops create cluster \
  --name=k8s.blog.taufek.dev \
  --state=s3://k8s.blog.taufek.dev.state \
  --zones=ap-southeast-1a,ap-southeast-1b,ap-southeast-1c \
  --master-volume-size=8 \
  --node-volume-size=8 \
  --dns-zone=k8s.blog.taufek.dev \
  --dry-run \
  --output yaml | tee k8s.blog.taufek.dev.yml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;For master and nodes volume, you could specify via the command line above but for etcd cluster, you will need to modify the cluster yaml file manually.&lt;/p&gt;

&lt;p&gt;Then edit the &lt;code&gt;k8s.blog.taufek.dev.yml&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: kops.k8s.io/v1alpha2
kind: Cluster
...
spec:
  ...
  etcdClusters:
  - cpuRequest: 200m
    etcdMembers:
    - instanceGroup: master-ap-southeast-1a
      name: a
      volumeSize: 8 # Add this volume in GB
    ...
  - cpuRequest: 100m
    etcdMembers:
    - instanceGroup: master-ap-southeast-1a
      name: a
      volumeSize: 8 # Add this volume in GB
    ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then run below command to create the cluster&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kops create -f k8s.blog.taufek.dev.yml --state=s3://k8s.blog.taufek.dev.state
kops create secret --name k8s.blog.taufek.dev ssh publickey admin -i ~/.ssh/id_rsa.pub
kops update cluster k8s.taufek.dev --yes --state=s3://k8s.blog.taufek.dev.state
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Existing Cluster
&lt;/h3&gt;

&lt;p&gt;Run below command to open up the Cluster object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kops edit cluster --state=s3://k8s.blog.taufek.dev.state
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Within the Cluster object, set below to configure master and nodes volumes size:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: kops.k8s.io/v1alpha2
kind: InstanceGroup
metadata:
  ...
  name: master-ap-southeast-1a
spec:
  ...
  role: Master
  rootVolumeSize: 8 # Add this value in GB

---

apiVersion: kops.k8s.io/v1alpha2
kind: InstanceGroup
metadata:
  ...
  name: nodes
spec:
  ...
  role: Node
  rootVolumeSize: 8 # Add this value in GB
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then run below to push the change to your cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kops update cluster --yes --state=s3://k8s.blog.taufek.dev.state
kops rolling-update cluster --yes --state=s3://k8s.blog.taufek.dev.state
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;For etcd volumes, you can’t use &lt;code&gt;kops&lt;/code&gt; to modify the volume size. But there are workaround mentioned in this &lt;a href="https://medium.com/@int128/resize-etcd-volumes-on-kops-e499b3936383"&gt;article&lt;/a&gt;. I personally used this workaround to reduce my etcd cluster volumes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;Before I carried out above cost optimization steps, below were the Cluster setup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;1 master instance with on-demand instance&lt;/li&gt;
&lt;li&gt;2 nodes instances with on-demand instance&lt;/li&gt;
&lt;li&gt;1 master EBS volume with 64G volume size&lt;/li&gt;
&lt;li&gt;2 nodes EBS volume with 128G volume size each&lt;/li&gt;
&lt;li&gt;2 etcd EBS volume with 20G volume size each&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now the Cluster setup looks like below:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;1 master instance with spot instance (up to 90% price reduction)&lt;/li&gt;
&lt;li&gt;2 nodes instances with spot instance (up to 90% price reduction)&lt;/li&gt;
&lt;li&gt;1 master EBS volume with 8G volume size&lt;/li&gt;
&lt;li&gt;2 nodes EBS volume with 8G volume size each&lt;/li&gt;
&lt;li&gt;2 etcd EBS volume with 8G volume size each&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At the point of writing this post, my Cluster is just 2 days old. I will update this space about the monthly costs once I have to pay the AWS bill at the end of this month.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>aws</category>
    </item>
    <item>
      <title>Continuous Integration for Static Site with Kubernetes</title>
      <dc:creator>Taufek Johar</dc:creator>
      <pubDate>Wed, 27 May 2020 16:00:16 +0000</pubDate>
      <link>https://dev.to/taufek/continuous-integration-for-static-site-with-kubernetes-249c</link>
      <guid>https://dev.to/taufek/continuous-integration-for-static-site-with-kubernetes-249c</guid>
      <description>&lt;h1&gt;
  
  
  Problem Statement
&lt;/h1&gt;

&lt;p&gt;I always wanted to work with Kubernetes but the subject comes with steep learning curve.Especially for someone who does not work directly with software infrastructure on day-to-day basis. The best way to learn is to actually use it in actual setting. This is my first attempt to use Kubernetes to serve a content on the Internet.&lt;/p&gt;

&lt;h1&gt;
  
  
  What: Non-Fictitious CI/CD with Kubernetes
&lt;/h1&gt;

&lt;p&gt;My plan is to setup a working and simple Continuous Integration and Continuous Deployment (CI/CD) workflow that integrates with Kubernetes cluster.What can be simpler than deploying a static site. I might as well I start a new blog site.This blog site is the result of CI workflow that I’ll be describing below.&lt;/p&gt;

&lt;h1&gt;
  
  
  How: Services that Build and Deploy This Blog to Kubernetes Cluster
&lt;/h1&gt;

&lt;p&gt;Services involved in this CI:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;GitHub Action. Workflow orchestration tool that executes steps in CI.&lt;/li&gt;
&lt;li&gt;Github Package Registry. Docker image registry provided by Github.&lt;/li&gt;
&lt;li&gt;Kubernetes cluster. At the point of writing this post I’m running my cluster on AWS with spot instances provisioned by &lt;a href="https://github.com/kubernetes/kops"&gt;kops&lt;/a&gt;. Still evaluating if this is the cheapest method for running Kubernetes cluster. (This is for future blog post)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Below are the high level steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make changes to my blog repository.&lt;/li&gt;
&lt;li&gt;Push to origin with tag (i.e &lt;code&gt;v1.0.0&lt;/code&gt;). Pushing to master without tag will not trigger the build which allow me to push my changes to master multiple times without triggering the deployment step.&lt;/li&gt;
&lt;li&gt;GitHub Action will do its magic to checkout, build and push newly created image to Kubernetes cluster.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Below is the GitHub Action yml file which wires everthing together.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Docker

on:
  push:
    tags:
      - v*
env:
  IMAGE_NAME: blog

jobs:
  push:
    runs-on: ubuntu-latest
    if: github.event_name == 'push'

    steps:
      - uses: actions/checkout@v2

      - name: Retrieve Gem Bundles Cache
        uses: actions/cache@v1
        with:
          path: _bundle
          key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
          restore-keys: |
            ${{ runner.os }}-gems-

      - name: Generate site
        run: |
          mkdir -p _bundle/cache
          chmod a+rwx -R .
          docker-compose run jekyll bundle install
          docker-compose run jekyll bundle exec jekyll build --trace

      - name: Build image
        run: docker build . --file Dockerfile --tag $IMAGE_NAME

      - name: Check bundle
        run: |
          ls _bundle/gems

      - name: Log into registry
        run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login docker.pkg.github.com -u ${{ github.actor }} --password-stdin

      - name: Push image
        run: |
          IMAGE_ID=docker.pkg.github.com/${{ github.repository }}/$IMAGE_NAME

          # Change all uppercase to lowercase
          IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')

          # Strip git ref prefix from version
          VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')

          # Strip "v" prefix from tag name
          [["${{ github.ref }}" == "refs/tags/"*]] &amp;amp;&amp;amp; VERSION=$(echo $VERSION | sed -e 's/^v//')

          # Use Docker `latest` tag convention
          ["$VERSION" == "master"] &amp;amp;&amp;amp; VERSION=latest

          echo IMAGE_ID=$IMAGE_ID
          echo VERSION=$VERSION

          docker tag $IMAGE_NAME $IMAGE_ID:$VERSION
          docker push $IMAGE_ID:$VERSION

          echo "::set-env name=VERSION::$VERSION"

      - name: Check Version
        id: check_version
        run: |
          echo ::set-output name=version::$VERSION

      - name: Update k8s deployment
        uses: Consensys/kubernetes-action@master
        env:
          KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_DATA }}
        with:
          args: set image deployment/blog-deployment blog=docker.pkg.github.com/taufek/blog/blog:${{ steps.check_version.outputs.version }}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  Github Action Yaml Breakdown
&lt;/h1&gt;

&lt;p&gt;Trigger this Github Action workflow if the push to origin includes tag starting with &lt;code&gt;v&lt;/code&gt;. For example &lt;code&gt;v1.0.0&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;on:
  push:
    tags:
      - v*
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Standard steps to checkout and retrieve cache (if there is any).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  - uses: actions/checkout@v2

  - name: Retrieve Gem Bundles Cache
    uses: actions/cache@v1
    with:
      path: _bundle
      key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
      restore-keys: |
        ${{ runner.os }}-gems-
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Generate the static site via Jekyll command. I use Docker image to skip Jekyll setup steps.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  - name: Generate site
    run: |
      mkdir -p _bundle/cache
      chmod a+rwx -R .
      docker-compose run jekyll bundle install
      docker-compose run jekyll bundle exec jekyll build --trace
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Build new Docker image for this blog. &lt;code&gt;$IMAGE_NAME&lt;/code&gt; value is &lt;code&gt;blog&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  - name: Build image
    run: docker build . --file Dockerfile --tag $IMAGE_NAME
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Login to Github Package Registry. This is required before pushing the image to the registry.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  - name: Log into registry
    run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login docker.pkg.github.com -u ${{ github.actor }} --password-stdin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Push the Docker image registry as &lt;code&gt;docker.pkg.github.com/taufek/blog/blog:1.0.0&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  - name: Push image
    run: |
      IMAGE_ID=docker.pkg.github.com/${{ github.repository }}/$IMAGE_NAME

      # Change all uppercase to lowercase
      IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')

      # Strip git ref prefix from version
      VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')

      # Strip "v" prefix from tag name
      [["${{ github.ref }}" == "refs/tags/"*]] &amp;amp;&amp;amp; VERSION=$(echo $VERSION | sed -e 's/^v//')

      # Use Docker `latest` tag convention
      ["$VERSION" == "master"] &amp;amp;&amp;amp; VERSION=latest

      echo IMAGE_ID=$IMAGE_ID
      echo VERSION=$VERSION

      docker tag $IMAGE_NAME $IMAGE_ID:$VERSION
      docker push $IMAGE_ID:$VERSION

      echo "::set-env name=VERSION::$VERSION"

  - name: Check Version
    id: check_version
    run: |
      echo ::set-output name=version::$VERSION
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Update Kubernetes Deployment with new image tag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  - name: Update k8s deployment
    uses: Consensys/kubernetes-action@master
    env:
      KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_DATA }}
    with:
      args: set image deployment/blog-deployment blog=docker.pkg.github.com/taufek/blog/blog:${{ steps.check_version.outputs.version }}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You can view the blog repository here &lt;a href="https://github.com/taufek/blog"&gt;taufek/blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I’m planning to post more Kubernetes content in the future as I learn more about Kubernetes.&lt;/p&gt;

&lt;p&gt;Keep watching this space.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>github</category>
    </item>
  </channel>
</rss>
