<?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>Forem: JL</title>
    <description>The latest articles on Forem by JL (@otter13).</description>
    <link>https://forem.com/otter13</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%2F991108%2F9b806e2f-41d7-4f25-b2a3-acc0ba560fd7.png</url>
      <title>Forem: JL</title>
      <link>https://forem.com/otter13</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/otter13"/>
    <language>en</language>
    <item>
      <title>Batect in a nutshell</title>
      <dc:creator>JL</dc:creator>
      <pubDate>Wed, 03 Apr 2024 23:24:50 +0000</pubDate>
      <link>https://forem.com/otter13/batect-in-a-nutshell-3f1g</link>
      <guid>https://forem.com/otter13/batect-in-a-nutshell-3f1g</guid>
      <description>&lt;p&gt;Batect is an alternative Docker Compose. For my own experience it is easier to use in terms of built-in features such as cross platform compatibility.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Unfortunately it is no longer maintained.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Its core is a yaml file that defines what containers to use and what tasks to execute. For example, I have one container called &lt;code&gt;run-env&lt;/code&gt; and a task &lt;code&gt;test-local&lt;/code&gt; which runs a line of command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;containers:
  run-env:
    build_directory: .
    volumes:
      - local: .
        container: /code
      - type: cache
        container: /code/node_modules
        name: node_modules
    working_directory: /workdir
tasks:
  test-local:
    description: Run Playwright e2e Tests
    run:
      container: run-env
      command: |
        npx playwright test
      environment:
        ENVIRONMENT: local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quick command to run locally:&lt;br&gt;
&lt;code&gt;./batect test-local&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Issue: permission denied: ./batect
&lt;/h3&gt;

&lt;p&gt;Run&lt;br&gt;
&lt;code&gt;chmod +x ./batect &lt;br&gt;
&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Issue: Checksum unmatched for the download behind firewall
&lt;/h3&gt;

&lt;p&gt;When running './batect ' for the first time, batect will download jar from remote server.&lt;br&gt;
Downloading Batect version 0.84.0 from &lt;em&gt;&lt;a href="https://updates.batect.dev/v1/files/0.84.0/batect-0.84.0.jar"&gt;https://updates.batect.dev/v1/files/0.84.0/batect-0.84.0.jar&lt;/a&gt;&lt;/em&gt;&lt;br&gt;
To&lt;br&gt;
&lt;code&gt;/Users/{user}/.batect/cache/0.84.0/batect-0.84.0.jar&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You might run into an issue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The downloaded version of Batect does not have the expected checksum. Delete '/Users/{user}/.batect/cache/0.84.0/batect-0.84.0.jar' and then re-run this script to download it again.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, the solution is to paste the url of jar into Chrome to download directly, and overwrite the local jar file.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Testing Spring Application (kotlin) with kotest, jacoco and mockk</title>
      <dc:creator>JL</dc:creator>
      <pubDate>Tue, 12 Sep 2023 07:07:03 +0000</pubDate>
      <link>https://forem.com/otter13/testing-spring-application-kotlin-with-kotest-and-mockk-3cmh</link>
      <guid>https://forem.com/otter13/testing-spring-application-kotlin-with-kotest-and-mockk-3cmh</guid>
      <description>&lt;p&gt;&lt;strong&gt;Install Kotest&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In &lt;em&gt;build.gradle.kts&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Add a task for Test&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tasks.withType&amp;lt;Test&amp;gt; {
    useJUnitPlatform()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under &lt;strong&gt;&lt;em&gt;test dependencies&lt;/em&gt;&lt;/strong&gt; section&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Kotest on the JVM uses the JUnit Platform gradle plugin.&lt;br&gt;
Kotest offers a Spring extension that allows you to test code that uses the Spring framework for dependency injection.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    testImplementation("io.kotest:kotest-runner-junit5-jvm:5.6.2")
    testImplementation("io.kotest.extensions:kotest-extensions-spring:1.1.3")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Installing the Kotest IntelliJ IDEA Plugin
It makes things convenient when you want to run tests from editor via a click.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On IntelliJ, go to File &amp;gt; Settings... &amp;gt; Plugins. While on the Marketplace tab, search for "kotest," then select the first result and click on Install.&lt;/p&gt;

&lt;p&gt;Ref: &lt;a href="https://www.waldo.com/blog/kotest-get-started"&gt;https://www.waldo.com/blog/kotest-get-started&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pick Testing Styles&lt;/strong&gt;&lt;br&gt;
It seems the creator of kotest tried the best to cater for all sorts of users.&lt;br&gt;
These are just regular classes that extend one of the Kotest spec styles.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe

class StringCalculatorTest : FunSpec({

    test("empty string results in zero") {
        add("") shouldBe 0
    }
})

fun add(test: String) : Int {
    return 0
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Test Coverage&lt;/strong&gt;&lt;br&gt;
Add &lt;code&gt;jacoco&lt;/code&gt; in &lt;strong&gt;&lt;em&gt;plugins&lt;/em&gt;&lt;/strong&gt; section in gradle.&lt;br&gt;
Add following tasks and configs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/// Test Coverage
jacoco {
    toolVersion = "0.8.8"
    reportsDirectory.set(layout.buildDirectory.dir("jacoco"))
}

tasks.test {
    finalizedBy(tasks.jacocoTestReport, tasks.jacocoTestCoverageVerification)
}

tasks.jacocoTestReport {
    dependsOn(tasks.test)
    classDirectories.setFrom(
        sourceSets.main.get().output.asFileTree.matching {
            exclude("**/somepackage/**")
        }
    )
    reports {
        xml.required.set(true)
        html.required.set(true)
    }
}

tasks.jacocoTestCoverageVerification {
    //TODO update it to 0.9
    val minimumThresholdValue = "0.0".toBigDecimal()

    violationRules {
        classDirectories.setFrom(
            sourceSets.main.get().output.asFileTree.matching {
                exclude("**/somepackage/**")
            }
        )
        rule {
            limit {
                minimum = minimumThresholdValue
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ref: &lt;a href="https://www.baeldung.com/kotlin/kotest"&gt;https://www.baeldung.com/kotlin/kotest&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Use Custom CA to Bypass SELF_SIGNED_CERT_IN_CHAIN Error</title>
      <dc:creator>JL</dc:creator>
      <pubDate>Tue, 15 Aug 2023 05:59:41 +0000</pubDate>
      <link>https://forem.com/otter13/aws-iam-role-and-policies-15l1</link>
      <guid>https://forem.com/otter13/aws-iam-role-and-policies-15l1</guid>
      <description>&lt;p&gt;As a developer you might need to install packages that are hosted on artifact repositories that your VPN / fireware does not allow to access. E.g.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Downloading Chromium 116.0.5845.82 (playwright build v1076) from https://playwright-akamai.azureedge.net/builds/chromium/1076/chromium-mac-arm64.zip
Error: self signed certificate in certificate chain
    at TLSSocket.onConnectSecure (node:_tls_wrap:1539:34)
    at TLSSocket.emit (node:events:513:28)
    at TLSSocket._finishInit (node:_tls_wrap:953:8)
    at TLSWrap.ssl.onhandshakedone (node:_tls_wrap:734:12) {
  code: 'SELF_SIGNED_CERT_IN_CHAIN'
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In such case, you can set Custom CA while downloading.&lt;/p&gt;

&lt;p&gt;Install OpenSSL:&lt;br&gt;
&lt;code&gt;brew cleanup &amp;amp;&amp;amp; brew update&lt;br&gt;
brew install openssl -f&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Generate the private key to become a local CA:&lt;br&gt;
openssl genrsa -des3 -out myCA.key 2048&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir certs
cd certs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate a root certificate:&lt;br&gt;
&lt;code&gt;openssl req -x509 -new -nodes -key myCA.key -sha256 -days 1825 -out myCA.pem&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You should now have two files: &lt;br&gt;
myCA.key (your private key) and myCA.pem (your root certificate).&lt;/p&gt;

&lt;p&gt;Ref: &lt;a href="https://deliciousbrains.com/ssl-certificate-authority-for-local-https-development/"&gt;https://deliciousbrains.com/ssl-certificate-authority-for-local-https-development/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you can set CA while installing from remote:&lt;br&gt;
&lt;code&gt;NODE_EXTRA_CA_CERTS="{path}/certs/myCA.pem" npx &amp;lt;forbidden_pacakge&amp;gt; install&lt;/code&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>AWS IAM Role and Policies</title>
      <dc:creator>JL</dc:creator>
      <pubDate>Tue, 09 May 2023 05:18:39 +0000</pubDate>
      <link>https://forem.com/otter13/aws-iam-role-and-policies-15lj</link>
      <guid>https://forem.com/otter13/aws-iam-role-and-policies-15lj</guid>
      <description>&lt;p&gt;AWS IAM Role and Policies&lt;br&gt;
IAM role and policies can be composed either via Visual Editor in AWS, or via plain JSON.&lt;/p&gt;

&lt;p&gt;To follow the Least Privilege Principal, we can further restrict actions via Resource and Conditions.&lt;/p&gt;

&lt;p&gt;To find the required Resource/Conditions for particular action, see:&lt;br&gt;
&lt;a href="https://iam.cloudonaut.io/reference/#/"&gt;https://iam.cloudonaut.io/reference/#/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For condition, you can use the service-specific string matching, such&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      "Condition": {
         "StringEquals": {
            "aws:ResourceTag/Department": "Test"
         }
      }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ref:&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_ec2_securitygroups-vpc.html"&gt;https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_ec2_securitygroups-vpc.html&lt;/a&gt; &lt;/p&gt;

</description>
    </item>
    <item>
      <title>jq - Json Query Tool in bash</title>
      <dc:creator>JL</dc:creator>
      <pubDate>Fri, 14 Apr 2023 04:10:00 +0000</pubDate>
      <link>https://forem.com/otter13/jq-json-query-tool-in-bash-4c4a</link>
      <guid>https://forem.com/otter13/jq-json-query-tool-in-bash-4c4a</guid>
      <description>&lt;p&gt;Cheatsheet (with explanation on what the params do):&lt;br&gt;
&lt;a href="https://stedolan.github.io/jq/manual/"&gt;https://stedolan.github.io/jq/manual/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common Operations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Iterate all elements (and override some) within a JSON object&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
          echo $temp &amp;gt; json-override.json

          for key in $(jq '. | keys' json-override.json); do

              # remove starting and trailing quotes
              ref_key=`echo $key | tr -d '"'`
              ref_key=`echo $ref_key | tr -d ','`
              if [ $key != '[' ] &amp;amp;&amp;amp; [ $key != ']' ]
              then
                  ref_val=`jq \
                  --arg target_key $ref_key \
                  '.[$target_key]' json-override.json`
                  ref_val=`echo $ref_val | tr -d '"'`
                  # echo $ref_key = $ref_val

                  case $ref_val in
                    OLD VALUE)
                      ref_val='NEW VALUE'
                      ;;
                    *)
                      echo -n "Unknown secret"
                      exit 1
                      ;;
                  esac

                  temp=`echo $temp | \
                  jq \
                  --arg target_key $ref_key \
                  --arg target_val "$ref_val" \
                  '.[$target_key] |= $target_val'`
              fi
          done
          echo $temp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>GitHub Workflow - Data</title>
      <dc:creator>JL</dc:creator>
      <pubDate>Fri, 14 Apr 2023 03:46:13 +0000</pubDate>
      <link>https://forem.com/otter13/github-workflow-data-3p6o</link>
      <guid>https://forem.com/otter13/github-workflow-data-3p6o</guid>
      <description>&lt;p&gt;Data can be held in various places in GitHub workflow runs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Job-Level Variable&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      - name: Test accessing var in bash
        env:
          temp: ${{ github.event.inputs.json-override }}
        run: |
          echo $temp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;&lt;br&gt;
Can be used to pass information between sequential jobs.&lt;br&gt;
&lt;a href="https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/"&gt;https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Save state
  run: echo "{name}={value}" &amp;gt;&amp;gt; $GITHUB_STATE

- name: Set output
  run: echo "{name}={value}" &amp;gt;&amp;gt; $GITHUB_OUTPUT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Refer to output from previous job&lt;br&gt;
&lt;a href="https://docs.github.com/en/actions/using-jobs/defining-outputs-for-jobs"&gt;https://docs.github.com/en/actions/using-jobs/defining-outputs-for-jobs&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jobs:
  job1:
    runs-on: ubuntu-latest
    # Map a step output to a job output
    outputs:
      output1: ${{ steps.step1.outputs.test }}
      output2: ${{ steps.step2.outputs.test }}
    steps:
      - id: step1
        run: echo "test=hello" &amp;gt;&amp;gt; "$GITHUB_OUTPUT"
      - id: step2
        run: echo "test=world" &amp;gt;&amp;gt; "$GITHUB_OUTPUT"
  job2:
    runs-on: ubuntu-latest
    needs: job1
    steps:
      - env:
          OUTPUT1: ${{needs.job1.outputs.output1}}
          OUTPUT2: ${{needs.job1.outputs.output2}}
        run: echo "$OUTPUT1 $OUTPUT2"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Variables&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Environment variables&lt;/li&gt;
&lt;li&gt;Repository variables&lt;/li&gt;
&lt;li&gt;Organization variables&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: to use Environment variables, you have to specify the environment at job level.&lt;br&gt;
  job-abc:&lt;br&gt;
    environment: NONPROD&lt;/p&gt;

&lt;p&gt;Refer to as&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; ${{ vars['ADMIN_PASSWORD'] }} 
 ${{ vars.ADMIN_PASSWORD }} 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Secrets&lt;/strong&gt;&lt;br&gt;
It is similar to variable. However they are better protected:&lt;br&gt;
In the logs, any output form of secrets will be masked.&lt;/p&gt;

&lt;p&gt;Refer to as&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; ${{ secrets['ADMIN_PASSWORD'] }} 
 ${{ secrets.ADMIN_PASSWORD }} 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other restrictions found are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The bash shell being called in the job cannot access GitHub’s secret variables. The viable path is to assign them to env of the job and access the env in the bash.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;GitHub secret variables cannot be accessed by specifying the key dynamically. You must provide a static string. The best you can do is:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Matrix cannot be a solution as there is no way to share a writable variable between each run of matrix jobs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Github does not support saving the content which has masked secrets&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The only way to see the content of a secret is to output to a file and save it in artifact&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      - name: Temp Verify
        if: false
        run: |
          echo ${{ secrets[github.event.inputs.single-param-name] }} &amp;gt; temp.txt

      - name: Archive production artifacts
        if: false
        uses: actions/upload-artifact@v3
        with:
          name: dist-without-markdown
          path: |
            temp.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Matrix&lt;/strong&gt;&lt;br&gt;
Can have &lt;strong&gt;&lt;em&gt;one template job&lt;/em&gt;&lt;/strong&gt; run multiple times based on an array (or all possible combinations from multi-dimensional arrays).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jobs:
  matrix-poc:
    strategy:
      matrix:
        secret_name: ['A', 'B']

    steps:
      - name: Iterate
        run: |
          echo ${{ secrets[matrix.secret_name] }} 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Event Inputs&lt;/strong&gt;&lt;br&gt;
This is a read-only value which can only be assigned by user input when triggering the workflow manually. There are 4 data types: In addition to the default string type, we now support choice, boolean, and environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    inputs:
      environment-string:
        type: choice
        required: true
        description: Please select your deployment environment. Purely a literal string.
        default: dev
        options:
          - dev
          - tst

      environment-repo:
        description: 'Environment to run against. From what you specify in the repo'
        type: environment
        required: true

      debug:
        description: Debug mode or not
        required: false
        default: boolean

      # refer to as 
      # echo '${{ github.event.inputs.json-override }}' &amp;gt; json-override.json
      json-string-example:
        description: JSON string
        required: false
        default: '{
                      "key1": "value1",
                      "key2": "value2"
                  }'

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

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>GitHub Workflow - Action</title>
      <dc:creator>JL</dc:creator>
      <pubDate>Fri, 14 Apr 2023 03:11:45 +0000</pubDate>
      <link>https://forem.com/otter13/github-workflow-data-38ob</link>
      <guid>https://forem.com/otter13/github-workflow-data-38ob</guid>
      <description>&lt;p&gt;&lt;strong&gt;Github Action&lt;/strong&gt;&lt;br&gt;
Custom Github Action is for performing task which is re-used. The implementation can be in JS. Repository of the example:&lt;br&gt;
&lt;a href="https://github.com/otter13/override-with-secrets-action" rel="noopener noreferrer"&gt;https://github.com/otter13/override-with-secrets-action&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example structure of Github Action folder&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;.github/actions/override-with-secrets-action/action.yaml&lt;/em&gt;&lt;br&gt;
This is the interface to GitHub, specifying inputs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: 'override-with-secrets-action'
description: 'loverride values in input json with secrets'
inputs:
  jsonString:
    required: true
runs:
  using: 'node16'
  main: 'dist/index.js'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Caller side in workflow&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Code snippet for using the action in workflow

      - name: Checkout Source
        with:
          persist-credentials: false
        uses: actions/checkout@v3

      - name: aws-ssm-to-env
        uses: ./.github/actions/override-with-secrets-action
        with:
          jsonString: $

      - name: log envs
        env:
          output_secrets_override: ${{ env.output_secrets_override }}
        run: |
          echo $output_secrets_override
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;.github/actions/override-with-secrets-action/src/index.js&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const core = require('@actions/core');

const configureInputs = () =&amp;gt; {
  const inputjsonString = core.getInput('jsonString');

  return {
    jsonString: inputjsonString,
  }
}
const override = async ({
  jsonString
})=&amp;gt; {
  var overridenJsonString = ''
  const obj = JSON.parse(jsonString)
  for (var key in obj) {
    if (obj.hasOwnProperty(key)) {
        // TODO: override here
        console.log(key + " -&amp;gt; " + obj[key]);
    }
  }
  return overridenJsonString
}


const saveToEnv = (overridenJsonString) =&amp;gt; {
    core.exportVariable('output_secrets_override', overridenJsonString);
}

const run = async () =&amp;gt; {
  try {
    const { jsonString } = configureInputs();
    const overridenJsonString = await override({jsonString});
    saveToEnv(overridenJsonString);
  } catch (error) {
    core.setFailed(error.message);
  }
}

run();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Package.json&lt;/em&gt;&lt;br&gt;
Specifying what packages and the build command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "name": "override-with-secrets-action",
    "version": "1.0.0",
    "description": "",
    "main": "src/index.js",
    "scripts": {
        "build": "ncc build src/index.js -o dist"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "dependencies": {
        "@actions/core": "^1.6.0",
        "@actions/github": "^5.0.0",
        "build": "^0.1.4"
    },
    "devDependencies": {
        "@vercel/ncc": "^0.33.4"
    }
}

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

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>Terraform Notes</title>
      <dc:creator>JL</dc:creator>
      <pubDate>Sat, 11 Mar 2023 08:09:51 +0000</pubDate>
      <link>https://forem.com/otter13/terraform-notes-34e7</link>
      <guid>https://forem.com/otter13/terraform-notes-34e7</guid>
      <description>&lt;p&gt;Install the binary on Windows and set Path&lt;br&gt;
&lt;a href="https://developer.hashicorp.com/terraform/downloads"&gt;https://developer.hashicorp.com/terraform/downloads&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Find the example codes for provider (e.g. AWS)&lt;br&gt;
&lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs"&gt;https://registry.terraform.io/providers/hashicorp/aws/latest/docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Steps:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;init&lt;/code&gt;&lt;br&gt;
will parse the script and download the necessary plugins&lt;br&gt;
When Terraform initializes a new Terraform directory, it creates a lock file named &lt;em&gt;.terraform.lock.hcl&lt;/em&gt; and the _.terraform _directory.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;plan&lt;/code&gt;&lt;br&gt;
Will have a preview of what resources to be created (by comparing configuartion and local state file)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;apply&lt;/code&gt;&lt;br&gt;
makes the changes defined by your Terraform configuration to create, update, or destroy resources.&lt;/p&gt;

&lt;p&gt;Example codes:&lt;br&gt;
&lt;em&gt;first_ec2.tf&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "aws" {
  region     = "us-west-2"
  access_key = "PUT-YOUR-ACCESS-KEY-HERE"
  secret_key = "PUT-YOUR-SECRET-KEY-HERE"
}

resource "aws_instance" "myec2" {
   ami = "ami-082b5a644766e0e6f"
   instance_type = "t2.micro"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform init
terraform plan
terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Quick steps for configuring provider AWS for Terraform&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a free-tier account in AWS&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Log in using root user &lt;br&gt;
Go to IAM &amp;gt; create a user &amp;gt; attach policy as Administrator&lt;br&gt;
Use following instructions to create access key and secert pair&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/powershell/latest/userguide/pstools-appendix-sign-up.html"&gt;https://docs.aws.amazon.com/powershell/latest/userguide/pstools-appendix-sign-up.html&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Then add this element in your main.tf&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "aws" {
  region     = "us-west-2"
  access_key = "my-access-key"
  secret_key = "my-secret-key"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/powershell/latest/userguide/pstools-appendix-sign-up.html"&gt;https://docs.aws.amazon.com/powershell/latest/userguide/pstools-appendix-sign-up.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;============&lt;br&gt;
&lt;strong&gt;BASIC&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Variable&lt;/strong&gt;&lt;br&gt;
Allowing you to specify value other than hardcoded in the main.tf. E.g. &lt;code&gt;terraform apply -var "instance_name=YetAnotherName"&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; resource "aws_instance" "app_server" {
   ami           = "ami-08d70e59c07c61a3a"
   instance_type = "t2.micro"

   tags = {
-    Name = "ExampleAppServerInstance"
+    Name = var.instance_name
   }
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://developer.hashicorp.com/terraform/tutorials/aws-get-started/aws-variables"&gt;https://developer.hashicorp.com/terraform/tutorials/aws-get-started/aws-variables&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Outputs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output "instance_id" {
  description = "ID of the EC2 instance"
  value       = aws_instance.app_server.id
}

output "instance_public_ip" {
  description = "Public IP address of the EC2 instance"
  value       = aws_instance.app_server.public_ip
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you can see in the log after running &lt;code&gt;apply&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Changes to Outputs:
  + instance_id        = "i-0bf954919ed765de1"
  + instance_public_ip = "54.186.202.254"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://developer.hashicorp.com/terraform/tutorials/aws-get-started/aws-outputs"&gt;https://developer.hashicorp.com/terraform/tutorials/aws-get-started/aws-outputs&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Version of project and Terraform&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://developer.hashicorp.com/terraform/tutorials/configuration-language/versions#inspect-the-terraform-state-file"&gt;https://developer.hashicorp.com/terraform/tutorials/configuration-language/versions#inspect-the-terraform-state-file&lt;/a&gt;&lt;br&gt;
The state file is generated/updated after running a plan/apply. It is a snapshot of the actual remote cloud resource.&lt;br&gt;
  "version": 4,&lt;br&gt;
  "serial": 8,&lt;br&gt;
  "terraform_version": "0.15.0",&lt;/p&gt;

&lt;p&gt;Note: when you update your project's &lt;strong&gt;&lt;em&gt;main.tf&lt;/em&gt;&lt;/strong&gt; and execute &lt;code&gt;terraform apply&lt;/code&gt;, the serial number will bump up by 1 or 2.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;&lt;em&gt;terraform.tf&lt;/em&gt;&lt;/strong&gt; file (similar to &lt;strong&gt;&lt;em&gt;Package.json&lt;/em&gt;&lt;/strong&gt;) contains the required version constrains of Terraform Core and plug-ins (=providers). There is no convention that &lt;strong&gt;&lt;em&gt;terraform.tf&lt;/em&gt;&lt;/strong&gt; must be the one. Sometimes you will see in the tutorial that &lt;strong&gt;versions.tf_&lt;/strong&gt; is used. So treat all *.tf as *.js in a node.js project.&lt;/p&gt;

&lt;p&gt;This configuration sets required_version to ~&amp;gt; 0.12.29. The ~&amp;gt; symbol allows the patch version to be greater than 29 but requires the major and minor versions (0.12) to match the version that the configuration specifies. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Versions of providers in lock files&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;constraints&lt;/li&gt;
&lt;li&gt;side effects of version upgrade&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;terraform.lock.hcl&lt;/em&gt;&lt;/strong&gt; (HashiCorp configuration language) is a middle product of terraform.lock.hcl is a middle product of init. It is similar to &lt;strong&gt;&lt;em&gt;Package.json.lock&lt;/em&gt;&lt;/strong&gt;. It is similar to Package-lock.json in npm.&lt;/p&gt;

&lt;p&gt;Terraform automatically creates or updates the dependency lock file each time you run the terraform init command.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Re-initialize your configuration with the -upgrade flag. This tells Terraform to download the new version of the provider, and update the version and signature in the lock file.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.hashicorp.com/terraform/tutorials/configuration-language/provider-versioning"&gt;https://developer.hashicorp.com/terraform/tutorials/configuration-language/provider-versioning&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;Providers&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://registry.terraform.io/browse/providers"&gt;https://registry.terraform.io/browse/providers&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;Define Infrastructure with Terraform Resources&lt;/strong&gt;&lt;br&gt;
Full run of a typical EC2 with security group + user data script to initiate a web application:&lt;br&gt;
&lt;a href="https://developer.hashicorp.com/terraform/tutorials/configuration-language/resource"&gt;https://developer.hashicorp.com/terraform/tutorials/configuration-language/resource&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;2 key learnings from the above are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Know how to use docs of the plugin&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3xrsD6Rq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nfh639b9u2cx6h0o3kqu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3xrsD6Rq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nfh639b9u2cx6h0o3kqu.png" alt="Image description" width="800" height="578"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Output information of each 'apply'
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output "domain-name" {
  value = aws_instance.web.public_dns
}

output "application-url" {
  value = "${aws_instance.web.public_dns}/index.php"
}

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

&lt;/div&gt;




&lt;p&gt;The previous 2 tutorials and this following one are basically talking about the same:&lt;br&gt;
&lt;a href="https://developer.hashicorp.com/terraform/tutorials/cli/init"&gt;https://developer.hashicorp.com/terraform/tutorials/cli/init&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above one is more exploring the process of how Terraform gets the providers and modules, into local file system.&lt;/p&gt;

&lt;p&gt;The main.tf contains reference to 1 local module and 1 remote module. Module is more like function for quick implementation to do some task, relying on other providers. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Providers&lt;/strong&gt;, which are plugins for Terraform that extend it with support for interacting with various external systems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modules&lt;/strong&gt;, which allow splitting out groups of Terraform configuration constructs (written in the Terraform language) into reusable abstractions.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module "nginx-pet" {
  source = "./nginx"

  container_name = "hello-${random_pet.dog.id}"
  nginx_port = 8001
}

module "hello" {
  source  = "joatmon08/hello/random"
  version = "3.0.1"

  hello = random_pet.dog.id

    secret_key = "secret"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;p&gt;&lt;strong&gt;Terraform Cloud (doing execution and storing states in remote)&lt;/strong&gt;&lt;br&gt;
After registration, use token (if you configure remote in main.tf you will be asked to use login with token) and creating an organisation:&lt;/p&gt;

&lt;p&gt;All the &lt;code&gt;apply&lt;/code&gt; will  be executed remotely (as github workflow)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QJjxEnOs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sjvy4bd9gefc1jvxudlt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QJjxEnOs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sjvy4bd9gefc1jvxudlt.png" alt="Image description" width="800" height="388"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://developer.hashicorp.com/terraform/tutorials/aws-get-started/aws-remote"&gt;https://developer.hashicorp.com/terraform/tutorials/aws-get-started/aws-remote&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Import&lt;/strong&gt;&lt;br&gt;
Command import is to syncrhonise from an existing infrastructure into terraform state. The aim is to bring that infrastructure under terraform's control.&lt;br&gt;
&lt;a href="https://developer.hashicorp.com/terraform/tutorials/state/state-import"&gt;https://developer.hashicorp.com/terraform/tutorials/state/state-import&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note: The tutorial uses a hardcoded host value docker.sock in docker provider.&lt;br&gt;
The solution is here: you will need to find out what a working docker.sock is in your own machine, and replace the docker.sock in docker provider.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;Commands for state management&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;terraform.tfstate contains the resource state of last sync. If you run &lt;code&gt;terraform plan&lt;/code&gt;, Terraform will calculate the difference of configuration and state, to figure out what to &lt;code&gt;apply&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The relationship of 3 entities is:&lt;br&gt;
Configuration (via &lt;code&gt;apply&lt;/code&gt;)-&amp;gt; Physical resource (after &lt;code&gt;apply&lt;/code&gt; or via &lt;code&gt;refresh&lt;/code&gt;)-&amp;gt; Local state&lt;br&gt;
(tutorial: &lt;a href="https://developer.hashicorp.com/terraform/tutorials/state/state-cli"&gt;https://developer.hashicorp.com/terraform/tutorials/state/state-cli&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;replace&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
Terraform usually only updates your infrastructure if it does not match your configuration. You can use the -replace flag for terraform plan and terraform apply operations to safely recreate resources in your environment even if you have not edited the configuration, which can be useful in cases of system malfunction.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;terraform plan -replace="aws_instance.example"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;show&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
Get a human-friendly output of the resources contained in your state.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;terraform show&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;state list&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
Run &lt;code&gt;terraform state list&lt;/code&gt; to get the list of resource names and local identifiers in your state file. This command is useful for more complex configurations where you need to find a specific resource without parsing state with &lt;code&gt;terraform show&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; terraform state list
data.aws_ami.ubuntu
aws_instance.example
aws_security_group.sg_8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;state mv&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;terraform state mv&lt;/code&gt; command moves resources from one state file to another. You can also rename resources with &lt;code&gt;mv&lt;/code&gt;. Moving resources is useful when you want to combine modules or resources from other states, but do not want to destroy and recreate the infrastructure.&lt;/p&gt;

&lt;p&gt;Note: The move command will update the resource in state, but not in your configuration file. You have to add the corresponding resource in the configuration as well, sperately.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;terraform state mv -state-out=../terraform.tfstate aws_instance.example_new aws_instance.example_new&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;state rm&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
The terraform state rm subcommand removes specific resources from your state file. This does not remove the resource from your configuration or destroy the infrastructure itself.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;terraform state rm aws_security_group.sg_8080&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;terraform refresh&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
Updates the state file when physical resources change outside of the Terraform workflow.&lt;br&gt;
The &lt;em&gt;-refresh-only&lt;/em&gt; flag was introduced in Terraform 0.15.4, and is preferred over the _terraform refres_h subcommand.&lt;br&gt;
In previous versions of Terraform, the only way to refresh your state file was by using the terraform refresh subcommand. However, this was less safe than the -refresh-only plan and apply mode since it would automatically overwrite your state file without giving you the option to review the modifications first. In this case, that would mean automatically dropping all of your resources from your state file.&lt;/p&gt;

&lt;p&gt;Use AWS cli to modify the physcial infrastructure outside terraform flow. E.g. delete an EC2 instance&lt;br&gt;
&lt;code&gt;aws ec2 terminate-instances --instance-ids $(terraform output -raw instance_id)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And then use &lt;code&gt;terraform refresh&lt;/code&gt; to sync from physcial infrastructure into local state file&lt;/p&gt;

&lt;p&gt;And then use &lt;code&gt;terraform plan&lt;/code&gt; to check the difference between &lt;strong&gt;configuration&lt;/strong&gt; and the &lt;strong&gt;state&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;terraform destroy&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Note:&lt;br&gt;
The unsync situation is defined as Resource Drift. The same command set (refresh and import) are covered in this tutorial:&lt;br&gt;
&lt;a href="https://developer.hashicorp.com/terraform/tutorials/state/resource-drift"&gt;https://developer.hashicorp.com/terraform/tutorials/state/resource-drift&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;Trouble-shoot (logging, support ticket)&lt;/p&gt;

&lt;p&gt;To switch on logging:&lt;br&gt;
&lt;a href="https://www.phillipsj.net/posts/how-to-configure-logging-for-terraform/"&gt;https://www.phillipsj.net/posts/how-to-configure-logging-for-terraform/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;PowerShell&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; $env:TF_LOG="TRACE"
&amp;gt; $env:TF_LOG_PATH="terraform.txt"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bash&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ export TF_LOG="TRACE"
$ export TF_LOG_PATH="terraform.txt"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tutorial: &lt;a href="https://developer.hashicorp.com/terraform/tutorials/configuration-language/troubleshooting-workflow#enable-terraform-logging"&gt;https://developer.hashicorp.com/terraform/tutorials/configuration-language/troubleshooting-workflow#enable-terraform-logging&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Modules&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Tutorial:&lt;br&gt;
&lt;a href="https://developer.hashicorp.com/terraform/tutorials/modules/module-create"&gt;https://developer.hashicorp.com/terraform/tutorials/modules/module-create&lt;/a&gt;&lt;br&gt;
This tutorial contains a &lt;strong&gt;consuming project&lt;/strong&gt; and a &lt;strong&gt;local module&lt;/strong&gt; within it. The module encapsulates the actions to create a website hosting-pueposed s3 bucket which allows public access. The project uses the module by passing some inputs such as bucket name as the website name.&lt;/p&gt;

&lt;p&gt;In summary, for a module:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create scaffolder contains tf files and LICENSE + README.md&lt;/li&gt;
&lt;li&gt;Define proper input vars and outputs&lt;/li&gt;
&lt;li&gt;A consumer terraform to use the module to do stuff&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The commands used in this tutorial:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;terraform get&lt;/code&gt;&lt;br&gt;
It is a light command to install modules.&lt;br&gt;
In this case, Terraform will install the required 2 modules from registry, and install 1 local module by directly referring to its folder within your project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Downloading registry.terraform.io/terraform-aws-modules/ec2-instance/aws 4.3.0 for ec2_instances...
- ec2_instances in .terraform\modules\ec2_instances
Downloading registry.terraform.io/terraform-aws-modules/vpc/aws 3.18.1 for vpc...
- vpc in .terraform\modules\vpc
- website_s3_bucket in modules\aws-s3-static-website-bucket
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;terraform output&lt;/code&gt;&lt;br&gt;
The terraform output command is used to extract the value of an output variable from the state file.&lt;br&gt;
&lt;a href="https://developer.hashicorp.com/terraform/cli/commands/output"&gt;https://developer.hashicorp.com/terraform/cli/commands/output&lt;/a&gt;&lt;br&gt;
In this case we run &lt;code&gt;terraform output&lt;/code&gt; to get the generated bucket website domain name so the developer knows easily what url to test with . E.g. &lt;em&gt;https://.s3-us-west-2.amazonaws.com/index.html&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Variables and Outputs&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
Tutorial&lt;br&gt;
&lt;a href="https://developer.hashicorp.com/terraform/tutorials/modules/module-use"&gt;https://developer.hashicorp.com/terraform/tutorials/modules/module-use&lt;/a&gt;&lt;br&gt;
The principal is to:&lt;/p&gt;

&lt;p&gt;(Inputs)&lt;br&gt;
Define variables in a standalone file variables.tf. E.g.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;variable "vpc_name" {
  description = "Name of VPC"
  type        = string
  default     = "example-vpc"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Refer to the variables and assign them to the inputs of modules within main.tf:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  ...
  name = var.vpc_name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Outputs)&lt;br&gt;
Refer to the outputs of modules as outputs within main.tf:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output "ec2_instance_public_ips" {
  description = "Public IP addresses of EC2 instances"
  value       = module.ec2_instances[*].public_ip
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;Collection Operation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Tutorial:&lt;br&gt;
&lt;a href="https://developer.hashicorp.com/terraform/tutorials/configuration-language/troubleshooting-workflow#correct-a-for_each-error"&gt;https://developer.hashicorp.com/terraform/tutorials/configuration-language/troubleshooting-workflow#correct-a-for_each-error&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case, the configuration defines a map collection of &lt;em&gt;aws_instance&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_instance" "web_app" {
   for_each               = local.security_groups
   ami                    = data.aws_ami.ubuntu.id
   instance_type          = "t2.micro"
   vpc_security_group_ids = [each.value]
   tags = {
    Name = "${var.name}-learn-${each.key}"
   }
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the outputs refer to the collection of &lt;em&gt;aws_instance&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; output "instance_id" {
   description = "ID of the EC2 instance"
    value       = [for instance in aws_instance.web_app: instance.id]
 }

 output "instance_public_ip" {
   description = "Public IP address of the EC2 instance"
    value       = [for instance in aws_instance.web_app: instance.public_ip]
 }

output "instance_name" {
   description = "Tags of the EC2 instance"
   value        = [for instance in aws_instance.web_app: instance.tags.Name]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The outputs print:&lt;br&gt;
&lt;/p&gt;

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

instance_id = [
  "i-0a679d5acef2b8e01",
  "i-02bd8b86bfa93ae96",
]
instance_name = [
  "terraform-learn-sg_8080",
  "terraform-learn-sg_ping",
]
instance_public_ip = [
  "13.58.116.31",
  "3.131.82.166",
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>Github Workflow</title>
      <dc:creator>JL</dc:creator>
      <pubDate>Sat, 25 Feb 2023 07:39:31 +0000</pubDate>
      <link>https://forem.com/otter13/github-workflow-jo7</link>
      <guid>https://forem.com/otter13/github-workflow-jo7</guid>
      <description>&lt;p&gt;&lt;strong&gt;Events&lt;/strong&gt;&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;on:
  push:
    paths-ignore:
      - 'docs/**'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ref:&lt;br&gt;
&lt;a href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet" rel="noopener noreferrer"&gt;https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Alternatively, developers can also add strings in the commit comments to skip manually:&lt;br&gt;
&lt;code&gt;[skip ci]&lt;/code&gt;&lt;br&gt;
&lt;a href="https://docs.github.com/en/actions/managing-workflow-runs/skipping-workflow-runs" rel="noopener noreferrer"&gt;https://docs.github.com/en/actions/managing-workflow-runs/skipping-workflow-runs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Steps&lt;/strong&gt;&lt;br&gt;
can be shell scripts, or actions (using "&lt;code&gt;with&lt;/code&gt;" to configure).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jobs: In Parallel vs Sequential&lt;/strong&gt;&lt;br&gt;
Jobs can be sequential be specifying "&lt;code&gt;needs: [job1, job2]&lt;/code&gt;" keywords.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expressions &amp;amp; Context Objects&lt;/strong&gt;&lt;br&gt;
e.g.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;run: echo "${{ toJSON(github) }}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Example NodeJS project workflow&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Deploy Project
on: [push, workflow_dispatch]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Get code
        uses: actions/checkout@v3
      - name: Install NodeJS
        uses: actions/setup-node@v3
        with:
          node-version: 18
      - name: Install dependencies
        run: npm ci
      - name: Run tests
        run: npm test
  deploy:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - name: Get code
        uses: actions/checkout@v3
      - name: Install NodeJS
        uses: actions/setup-node@v3
        with:
          node-version: 18
      - name: Install dependencies
        run: npm ci
      - name: Build project
        run: npm run build
      - name: Deploy
        run: echo "Deploying ..."

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

&lt;/div&gt;



&lt;p&gt;Useful actions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Uploading Job Artifacts&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://github.com/actions/upload-artifact" rel="noopener noreferrer"&gt;https://github.com/actions/upload-artifact&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Downloading Artifacts&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://github.com/actions/download-artifact" rel="noopener noreferrer"&gt;https://github.com/actions/download-artifact&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
**Job Outputs**
find dist/assets/*.js -type f -execdir echo 'my-variable={}' &amp;gt;&amp;gt; $GITHUB_OUTPUT ';'
later on, this variable can be accessed via:

outputs:
    script-file: {{ steps.publis.outputs.my-variable }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Sharing outputs across multiple jobs&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;needs&lt;/code&gt;&lt;br&gt;
&lt;a href="https://docs.github.com/en/actions/learn-github-actions/contexts#needs-context" rel="noopener noreferrer"&gt;https://docs.github.com/en/actions/learn-github-actions/contexts#needs-context&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caching Dependencies for speeding up jobs&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows" rel="noopener noreferrer"&gt;https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows&lt;/a&gt;&lt;br&gt;
e.g. use cache action in both build and test, with same hash value in the cache name (to make sure they are using the same cache in the central place):&lt;/p&gt;

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

</description>
      <category>gratitude</category>
      <category>inspiration</category>
    </item>
    <item>
      <title>(WIP) Useful Docker Commands</title>
      <dc:creator>JL</dc:creator>
      <pubDate>Mon, 30 Jan 2023 01:32:19 +0000</pubDate>
      <link>https://forem.com/otter13/wip-useful-docker-commands-3509</link>
      <guid>https://forem.com/otter13/wip-useful-docker-commands-3509</guid>
      <description>&lt;p&gt;&lt;strong&gt;Quickly Debug a Docker File&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build . -t &amp;lt;name&amp;gt; 
docker image list
docker run -td &amp;lt;image&amp;gt;
docker ps
docker exec -it &amp;lt;container&amp;gt; /bin/bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;OS Arch (CPU) type&lt;/strong&gt;&lt;br&gt;
Checking the type of docker image/container&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dpkg --print-architecture
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Checking if your docker builder/node supports certain types&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker buildx ls
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Clean up&lt;/strong&gt;&lt;br&gt;
To stop all Docker containers, simply run the following command in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker kill $(docker ps -q)
docker rm $(docker ps -a -q)
docker rmi $(docker images -q)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To clear the cache of builder, use following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker builder prune
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Create multi-platform image&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;List all buildx on the machine&lt;/em&gt;&lt;br&gt;
&lt;code&gt;docker buildx ls&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME/NODE       DRIVER/ENDPOINT STATUS  BUILDKIT PLATFORMS
default *       docker
  default       default         running 20.10.21 windows/amd64, linux/amd64
desktop-linux   docker
  desktop-linux desktop-linux   running 20.10.21 linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;run &lt;code&gt;docker context use desktop-linux&lt;/code&gt; to switch to context desktop-linux&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Use specified buildx&lt;/em&gt;&lt;br&gt;
&lt;code&gt;docker buildx use desktop-linux&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Check what the current buildx is&lt;/em&gt;&lt;br&gt;
&lt;code&gt;docker buildx inspect --bootstrap&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name:   default
Driver: docker

Nodes:
Name:      default
Endpoint:  desktop-linux
Status:    running
Buildkit:  20.10.21
Platforms: linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Time to build an image&lt;/em&gt;&lt;br&gt;
&lt;code&gt;docker buildx build . -t &amp;lt;image_name&amp;gt; --platform linux/arm64&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Inspect further on an instance of this image&lt;/em&gt;&lt;br&gt;
&lt;code&gt;docker run --rm &amp;lt;image_name&amp;gt; uname -m&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WARNING: The requested image's platform (linux/arm64) does not match the detected host platform (linux/amd64) and no specific platform was requested
aarch64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ref:&lt;br&gt;
&lt;a href="https://www.docker.com/blog/multi-arch-images/" rel="noopener noreferrer"&gt;https://www.docker.com/blog/multi-arch-images/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Conditional Logic in Docker Build *&lt;/em&gt;&lt;br&gt;
By using ARG and ENV&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM node:alpine

RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

COPY package.json /usr/src/app/
RUN npm install
COPY . /usr/src/app
ENV PORT 3000
ARG DOCKER_ENV
ENV NODE_ENV=${DOCKER_ENV}
RUN if [ "$DOCKER_ENV" = "stag" ] ; then  echo   your NODE_ENV for stage is $NODE_ENV;  \
else  echo your NODE_ENV for dev is $NODE_ENV; \
fi 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;when you build this Dockerfile with this command&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker build --build-arg DOCKER_ENV=stag -t test-node .&lt;br&gt;
&lt;/code&gt;You will see at layer&lt;/p&gt;

&lt;p&gt;&lt;code&gt;---&amp;gt; Running in a6231eca4d0b your NODE_ENV for stage is stag&lt;br&gt;
&lt;/code&gt;When you run this docker container and run this command your output will be&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
/usr/src/app # echo $NODE_ENV

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

&lt;/div&gt;



</description>
      <category>postmark</category>
      <category>productivity</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Web Dev Suite Setup on Debian VM for Windows</title>
      <dc:creator>JL</dc:creator>
      <pubDate>Sat, 28 Jan 2023 06:07:07 +0000</pubDate>
      <link>https://forem.com/otter13/web-dev-suite-setup-on-debian-vm-for-windows-47g7</link>
      <guid>https://forem.com/otter13/web-dev-suite-setup-on-debian-vm-for-windows-47g7</guid>
      <description>&lt;p&gt;MacOS, Windows and Linux are the 3 players which come across in a developer's daily life. Virtual machine is always the solution. In this article I will go through the steps on how to quickly setup an Debian Linux VM with all required gears (VSCode, Chrome, Docker) for front-end devs, on a Windows laptop.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Download and install &lt;strong&gt;VMWare Workstation Player&lt;/strong&gt;. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Download the &lt;strong&gt;ISO image of Debian installation&lt;/strong&gt; (even you are on intel CPU)&lt;br&gt;
&lt;a href="https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/" rel="noopener noreferrer"&gt;https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/&lt;/a&gt; &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In VMWare, create a new machine from ISO installer. This will take about 20 min.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Once Debian is installed, log in and use terminal to install &lt;strong&gt;gdebi&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt install gdebi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Now download &lt;strong&gt;Chrome installer&lt;/strong&gt; on Linux (.deb) and use &lt;strong&gt;gdebi&lt;/strong&gt; to load the package&lt;/li&gt;
&lt;/ol&gt;

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

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

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

&lt;p&gt;Alternatively, you can install using wget via terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wget --no-check-certificate https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
dpkg -i google-chrome-stable_current_amd64.deb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Download &lt;strong&gt;VS Code&lt;/strong&gt; (&lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;https://code.visualstudio.com/&lt;/a&gt;) and install from deb installer.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;Install Node
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt update
sudo apt upgrade
sudo apt install nodejs npm -y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;If you switch node versions often, install NVM as well
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt install curl 
curl https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash 
source ~/.profile   
nvm install 12.18.3  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Install &lt;strong&gt;docker desktop&lt;/strong&gt; on Debian
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt-get update
sudo apt-get install ca-certificates curl gnupg lsb-release
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list &amp;gt; /dev/null
sudo apt update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin
Then run sudo apt-get install ./docker-desktop-&amp;lt;version&amp;gt;-&amp;lt;arch&amp;gt;.deb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ref:&lt;br&gt;
&lt;a href="https://docs.docker.com/engine/install/debian/" rel="noopener noreferrer"&gt;https://docs.docker.com/engine/install/debian/&lt;/a&gt;&lt;br&gt;
&lt;a href="https://docs.docker.com/compose/install/linux/" rel="noopener noreferrer"&gt;https://docs.docker.com/compose/install/linux/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note:&lt;br&gt;
Update: snyk that comes with Docker Engine is not for arm64: you will get this error:&lt;br&gt;
exec /user/local/bin/snyk exec format error&lt;br&gt;
You will have to install the binary manually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl https://static.snyk.io/cli/latest/snyk-linux-arm64 -o snyk
chmod +x ./snyk
mv ./snyk /usr/local/bin/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;del&gt;If you are on arm64 Linux, your default installation will not contain Docker Scan plugin, and apt-get install docker-scan-plugin will not work. You will have to install manually:&lt;/del&gt;&lt;br&gt;
(&lt;a href="https://github.com/docker/scan-cli-plugin#on-linux" rel="noopener noreferrer"&gt;https://github.com/docker/scan-cli-plugin#on-linux&lt;/a&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir -p ~/.docker/cli-plugins &amp;amp;&amp;amp; \
curl https://github.com/docker/scan-cli-plugin/releases/latest/download/docker-scan_linux_arm64 -L -s -S -o ~/.docker/cli-plugins/docker-scan &amp;amp;&amp;amp;\
chmod +x ~/.docker/cli-plugins/docker-scan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>developers</category>
      <category>ai</category>
      <category>productivity</category>
      <category>discuss</category>
    </item>
    <item>
      <title>OAuth 2.0 - Role-based Access Control</title>
      <dc:creator>JL</dc:creator>
      <pubDate>Mon, 02 Jan 2023 10:33:17 +0000</pubDate>
      <link>https://forem.com/otter13/oauth-20-role-based-access-control-15e4</link>
      <guid>https://forem.com/otter13/oauth-20-role-based-access-control-15e4</guid>
      <description>&lt;p&gt;Another way to control access is Role based, which enables "grouping" the privilege that makes management easier. &lt;/p&gt;

&lt;p&gt;For example, the real life scenario is like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oN9Hd7XG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7hk6othlhm5qy2z5p0s0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oN9Hd7XG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7hk6othlhm5qy2z5p0s0.png" alt="Image description" width="880" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Again, 3-party play:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Define user and roles in Auth Server&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7SIhbxFJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6lw2t4srkg2grzsjna3k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7SIhbxFJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6lw2t4srkg2grzsjna3k.png" alt="Image description" width="880" height="284"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resource Server: Securing endpoints to a specific role&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new KeycloakRoleConverter());

        http
            .authorizeRequests()
                    .antMatchers(HttpMethod.GET, "/users/status/check")
                    .hasRole("developer")

                .anyRequest().authenticated()
                .and()
            .oauth2ResourceServer()
            .jwt()
            .jwtAuthenticationConverter(jwtAuthenticationConverter);
    }

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Resource Server: Mapping the JWT role, into Autority class reckognised by Spring.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the code, we will need to convert the role into spring-authorities&lt;br&gt;
Here is a trick on how to inspect the role from the token (&lt;a href="https://jwt.io/"&gt;https://jwt.io/&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Vo1Ni8E2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3o86ht0blmfuyzi2624p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Vo1Ni8E2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3o86ht0blmfuyzi2624p.png" alt="Image description" width="880" height="237"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class KeycloakRoleConverter implements Converter&amp;lt;Jwt, Collection&amp;lt;GrantedAuthority&amp;gt;&amp;gt; {

    @Override
    public Collection&amp;lt;GrantedAuthority&amp;gt; convert(Jwt jwt) {
        Map&amp;lt;String, Object&amp;gt; realmAccess = (Map&amp;lt;String, Object&amp;gt;) jwt.getClaims().get("realm_access");

        if (realmAccess == null || realmAccess.isEmpty()) {
            return new ArrayList&amp;lt;&amp;gt;();
        }

        Collection&amp;lt;GrantedAuthority&amp;gt; returnValue = ((List&amp;lt;String&amp;gt;) realmAccess.get("roles"))
                .stream().map(roleName -&amp;gt; "ROLE_" + roleName)  
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());

        return returnValue;
    }

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

&lt;/div&gt;



&lt;p&gt;Spring framework has 2 methods to check:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;hasAuthority("ROLE_developer");&lt;/code&gt; //you have to add the prefix ROLE_ by yourself.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hasRole("developer");&lt;/code&gt; // it is working the same as above, but without needing to add prefix ROLE_&lt;/li&gt;
&lt;/ol&gt;

</description>
    </item>
  </channel>
</rss>
