<?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: Toru Takahashi</title>
    <description>The latest articles on Forem by Toru Takahashi (@tttol).</description>
    <link>https://forem.com/tttol</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%2F1367296%2Ff967e30f-9e03-46b3-9c00-e7ad216ae390.JPG</url>
      <title>Forem: Toru Takahashi</title>
      <link>https://forem.com/tttol</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/tttol"/>
    <language>en</language>
    <item>
      <title>Listing IAM Roles for Your Current AWS Account</title>
      <dc:creator>Toru Takahashi</dc:creator>
      <pubDate>Sat, 10 Jan 2026 22:08:57 +0000</pubDate>
      <link>https://forem.com/aws-builders/listing-iam-roles-for-your-current-aws-account-24ie</link>
      <guid>https://forem.com/aws-builders/listing-iam-roles-for-your-current-aws-account-24ie</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Ever tried to create or update a resource in the AWS console, only to find out you don't have permission?&lt;/p&gt;

&lt;p&gt;If you've worked with AWS, I'm sure you've been there. I know I have - more times than I can count. Each time, I have to check which IAM roles are attached to my account and send a message to our infrastructure team asking for the necessary permissions.&lt;/p&gt;

&lt;p&gt;In most organizations, AWS accounts follow the principle of least privilege, which is a security best practice. It minimizes the risk of granting excessive permissions. However, figuring out exactly what permissions each person needs for their specific use case can be tricky.&lt;/p&gt;

&lt;p&gt;As your organization grows, so does the frequency of "I don't have permission" requests. When this happens, you'll want a quick way to check what roles your account actually has.&lt;/p&gt;

&lt;h1&gt;
  
  
  I Built a CLI Tool to List IAM Roles
&lt;/h1&gt;

&lt;p&gt;Since checking IAM manually every time gets tedious, I created a command-line tool that displays all attached roles at once. It's called canido. canido means "Can I do?".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tttol/canido" rel="noopener noreferrer"&gt;https://github.com/tttol/canido&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With canido, you can quickly see all the roles attached to your currently logged-in AWS account.&lt;br&gt;
Here's what it looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;% canido

&lt;span class="nt"&gt;---&lt;/span&gt; Checking AWS credentials &lt;span class="nt"&gt;---&lt;/span&gt;
Target role: AWSReservedSSO_CanidoInlinePolicy_f1d7ab46757a3473

&lt;span class="o"&gt;==================================================&lt;/span&gt;
  1. Managed Policies
&lt;span class="o"&gt;==================================================&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;Policy ARN]: arn:aws:iam::aws:policy/IAMFullAccess
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"Statement"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"Action"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"iam:*"&lt;/span&gt;,
        &lt;span class="s2"&gt;"organizations:DescribeAccount"&lt;/span&gt;,
        &lt;span class="s2"&gt;"organizations:DescribeOrganization"&lt;/span&gt;,
        &lt;span class="s2"&gt;"organizations:DescribeOrganizationalUnit"&lt;/span&gt;,
        &lt;span class="s2"&gt;"organizations:DescribePolicy"&lt;/span&gt;,
        &lt;span class="s2"&gt;"organizations:ListChildren"&lt;/span&gt;,
        &lt;span class="s2"&gt;"organizations:ListParents"&lt;/span&gt;,
        &lt;span class="s2"&gt;"organizations:ListPoliciesForTarget"&lt;/span&gt;,
        &lt;span class="s2"&gt;"organizations:ListRoots"&lt;/span&gt;,
        &lt;span class="s2"&gt;"organizations:ListPolicies"&lt;/span&gt;,
        &lt;span class="s2"&gt;"organizations:ListTargetsForPolicy"&lt;/span&gt;
      &lt;span class="o"&gt;]&lt;/span&gt;,
      &lt;span class="s2"&gt;"Effect"&lt;/span&gt;: &lt;span class="s2"&gt;"Allow"&lt;/span&gt;,
      &lt;span class="s2"&gt;"Resource"&lt;/span&gt;: &lt;span class="s2"&gt;"*"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;]&lt;/span&gt;,
  &lt;span class="s2"&gt;"Version"&lt;/span&gt;: &lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;--------------------------------------------------&lt;/span&gt;

&lt;span class="o"&gt;==================================================&lt;/span&gt;
  2. Inline Policies
&lt;span class="o"&gt;==================================================&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;Policy Name]: AwsSSOInlinePolicy
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"Statement"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"Action"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;,
        &lt;span class="s2"&gt;"s3:ListBucket"&lt;/span&gt;
      &lt;span class="o"&gt;]&lt;/span&gt;,
      &lt;span class="s2"&gt;"Effect"&lt;/span&gt;: &lt;span class="s2"&gt;"Allow"&lt;/span&gt;,
      &lt;span class="s2"&gt;"Resource"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"*"&lt;/span&gt;
      &lt;span class="o"&gt;]&lt;/span&gt;,
      &lt;span class="s2"&gt;"Sid"&lt;/span&gt;: &lt;span class="s2"&gt;"Statement1"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;,
    &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"Action"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"secretsmanager:DescribeSecret"&lt;/span&gt;,
        &lt;span class="s2"&gt;"secretsmanager:GetRandomPassword"&lt;/span&gt;,
        &lt;span class="s2"&gt;"secretsmanager:GetResourcePolicy"&lt;/span&gt;,
        &lt;span class="s2"&gt;"secretsmanager:GetSecretValue"&lt;/span&gt;,
        &lt;span class="s2"&gt;"secretsmanager:ListSecretVersionIds"&lt;/span&gt;,
        &lt;span class="s2"&gt;"secretsmanager:ListSecrets"&lt;/span&gt;,
        &lt;span class="s2"&gt;"secretsmanager:BatchGetSecretValue"&lt;/span&gt;
      &lt;span class="o"&gt;]&lt;/span&gt;,
      &lt;span class="s2"&gt;"Effect"&lt;/span&gt;: &lt;span class="s2"&gt;"Allow"&lt;/span&gt;,
      &lt;span class="s2"&gt;"Resource"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"*"&lt;/span&gt;
      &lt;span class="o"&gt;]&lt;/span&gt;,
      &lt;span class="s2"&gt;"Sid"&lt;/span&gt;: &lt;span class="s2"&gt;"Statement2"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;]&lt;/span&gt;,
  &lt;span class="s2"&gt;"Version"&lt;/span&gt;: &lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;--------------------------------------------------&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The implementation is straightforward - it calls the following AWS CLI commands in sequence and formats the output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;% aws sts get-caller-identity
% aws iam list-attached-role-policies &lt;span class="nt"&gt;--role-name&lt;/span&gt; MyRole
% aws iam get-policy-version &lt;span class="nt"&gt;--policy-arn&lt;/span&gt; ... &lt;span class="nt"&gt;--version-id&lt;/span&gt; ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For macOS users, installation is available via Homebrew. You'll need to tap the repository first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew tap tttol/tap &lt;span class="c"&gt;# Add the tap&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;canido &lt;span class="c"&gt;# Install canido&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Linux, you can install from the binary release. This method also works for macOS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# For x86_64 (Intel/AMD)&lt;/span&gt;
curl &lt;span class="nt"&gt;-LO&lt;/span&gt; https://github.com/tttol/canido/releases/latest/download/canido-x86_64-unknown-linux-gnu.tar.gz
&lt;span class="nb"&gt;tar &lt;/span&gt;xzf canido-x86_64-unknown-linux-gnu.tar.gz
&lt;span class="nb"&gt;sudo mv &lt;/span&gt;canido /usr/local/bin/
canido &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Windows users can follow the Linux instructions using WSL2. Unfortunately, I haven't prepared binaries for Command Prompt or PowerShell (mainly because I don't have a Windows environment to test with).&lt;/p&gt;

&lt;h1&gt;
  
  
  Design Decisions
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Focused on the Essentials
&lt;/h3&gt;

&lt;p&gt;I kept canido laser-focused on one thing: "display IAM roles attached to the currently logged-in AWS account." Any other features were deliberately left out. I could have added options like checking roles for other account IDs or displaying policies for specific role ARNs, but I decided against it.&lt;/p&gt;

&lt;p&gt;My motivation for creating canido was simple: "I want to know what my current account can do." Checking other accounts or roles fell outside that scope. The name "canido" itself comes from "Can I do?" - focusing on your own capabilities, not others'.&lt;/p&gt;

&lt;h3&gt;
  
  
  --short Option
&lt;/h3&gt;

&lt;p&gt;When you have many IAM roles or a single role contains numerous policies, the output can get long and hard to read. To address this, I added a &lt;code&gt;--short&lt;/code&gt; option that displays only the names of attached IAM roles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ canido &lt;span class="nt"&gt;--short&lt;/span&gt;
&lt;span class="nt"&gt;---&lt;/span&gt; Checking AWS credentials &lt;span class="nt"&gt;---&lt;/span&gt;
Target role: AWSReservedSSO_CanidoInlinePolicy_f1d7ab46757a3473

&lt;span class="o"&gt;==================================================&lt;/span&gt;
  1. Managed Policies
&lt;span class="o"&gt;==================================================&lt;/span&gt;
IAMFullAccess

&lt;span class="o"&gt;==================================================&lt;/span&gt;
  2. Inline Policies
&lt;span class="o"&gt;==================================================&lt;/span&gt;
AwsSSOInlinePolicy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Distributed via Homebrew
&lt;/h3&gt;

&lt;p&gt;Since I'm a Mac user, I wanted to distribute canido via Homebrew. This was my first time distributing a tool through Homebrew, so it involved a lot of research and trial and error.&lt;/p&gt;

&lt;p&gt;To distribute a package via Homebrew, you typically need to add it to the official homebrew/core repository. This requires meeting certain criteria, including being open source and having some level of popularity (like GitHub stars).&lt;/p&gt;

&lt;p&gt;For lesser-known tools like canido, getting into core is a high bar. Instead, I used the "tap" feature, which allows anyone to distribute packages without going through the review process. The trade-off is that users need to add one extra line to tap the repository before installing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew tap tttol/tap &lt;span class="c"&gt;# Add the tap&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;canido &lt;span class="c"&gt;# Install canido&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Wrapping Up
&lt;/h1&gt;

&lt;p&gt;I hope canido becomes useful for AWS users out there. If you find it helpful, please give it a star on GitHub!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>iam</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Developing an app for household management with AWS Amplify</title>
      <dc:creator>Toru Takahashi</dc:creator>
      <pubDate>Tue, 20 Aug 2024 02:25:02 +0000</pubDate>
      <link>https://forem.com/aws-builders/developing-an-app-for-household-management-with-aws-amplify-2nei</link>
      <guid>https://forem.com/aws-builders/developing-an-app-for-household-management-with-aws-amplify-2nei</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Hi everyone.&lt;/p&gt;

&lt;p&gt;In this article, I’m going to introduce the household budgeting app I’ve developed and currently operate using AWS Amplify (referred to as "Amplify" from here on). &lt;/p&gt;

&lt;p&gt;🚨Please note, this app is a personal project and is not intended for commercial use. It’s designed solely for use by my family and me.🚨&lt;/p&gt;

&lt;p&gt;💡Original japanese post is here.(I wrote this too)💡&lt;br&gt;
&lt;a href="https://qiita.com/tttol/items/d79b5e67de4c33e27858" rel="noopener noreferrer"&gt;https://qiita.com/tttol/items/d79b5e67de4c33e27858&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Intended Audience
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Anyone looking to solve problems using web applications&lt;/li&gt;
&lt;li&gt;Those who have heard of Amplify but don’t know the details&lt;/li&gt;
&lt;li&gt;People who want to develop web applications using AWS resources easily&lt;/li&gt;
&lt;li&gt;Those with app development knowledge but who are less confident in infrastructure&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  The problem of household management
&lt;/h1&gt;

&lt;p&gt;“Our problem: Eliminating the hassle of reimbursements between family members”&lt;/p&gt;

&lt;p&gt;My wife and I often talk about reimbursements. For example, if she goes shopping at the grocery store, she might temporarily cover the cost, and later I’ll pay her back for half of it.&lt;/p&gt;



&lt;p&gt;Wife: “I went shopping and covered the cost, so please pay me back later.”&lt;br&gt;
Me: “Thanks. Oh, by the way, I bought tissues at the pharmacy yesterday, so I’d like to offset that cost.”&lt;br&gt;
Wife: “Hmm, but I’m planning to go to the store tomorrow, so maybe we can include that too…”&lt;br&gt;
Me: “Ugh…” (This is where I get tired of thinking about it.)&lt;/p&gt;



&lt;p&gt;Doing these reimbursements every day is a hassle, so I wanted a way to manage these situations better. The first method I thought of was using Google Spread Sheets.&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%2Fk76a5fv73ehwggnici2y.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%2Fk76a5fv73ehwggnici2y.png" alt=" " width="800" height="825"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Both my wife and I would record the amounts and items we paid for, and we’d use a SUMIF function in the last row to calculate the final amount each of us needs to pay. We didn’t need to settle the balance daily; we could do it weekly or monthly.&lt;/p&gt;

&lt;p&gt;By sharing this spreadsheet between us, we aimed to make managing reimbursements easier.&lt;/p&gt;
&lt;h1&gt;
  
  
  Issues with Spreadsheet Management
&lt;/h1&gt;

&lt;p&gt;Although managing things through a spreadsheet made the process somewhat easier, new issues arose.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It’s hard to enter data into the spreadsheet from a smartphone

&lt;ul&gt;
&lt;li&gt;When entering data while out, you have to use the mobile app version of Google Spread Sheets.&lt;/li&gt;
&lt;li&gt;The screen is small…&lt;/li&gt;
&lt;li&gt;Data entry becomes a hassle, and receipts start piling up.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;When the table fills up, you need to add more rows, which is also hard on a smartphone.

&lt;ul&gt;
&lt;li&gt;As rows increase, scrolling becomes necessary.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;It’s hard to see at a glance how much you owe.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a result, &lt;strong&gt;I ended up doing most of the data entry on a PC at home when I had free time.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To solve these issues, I decided to turn the functionality of the spreadsheet into a web application.&lt;/p&gt;
&lt;h1&gt;
  
  
  Turning It into an App with Amplify
&lt;/h1&gt;

&lt;p&gt;I built the application with Next.js and deployed it using Amplify Hosting.&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%2Fz9iawwxjioq22z3l6fwx.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%2Fz9iawwxjioq22z3l6fwx.png" alt=" " width="780" height="1468"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;SUMMARY&lt;/code&gt; section at the top of the screen shows each person’s debt.&lt;/p&gt;

&lt;p&gt;The area below that shows a list of items that have been reimbursed. You can see that two items, &lt;code&gt;buy grocery&lt;/code&gt; and &lt;code&gt;tissue&lt;/code&gt;, have been entered. Based on these two items, the app calculates that the husband owes nothing, while the wife has a debt of 1,051 yen. (The method for calculating debt will be explained later.)&lt;/p&gt;

&lt;p&gt;You can add new items from the screen.&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%2F3nhwt12zljqveht1bn73.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%2F3nhwt12zljqveht1bn73.png" alt=" " width="776" height="1466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By improving the usability of the item creation screen, I addressed the issues with smartphone operation mentioned earlier. Dropdowns are used for label selection, a date picker for entering dates, and radio buttons for selecting the payer, making the interface more user-friendly.&lt;/p&gt;
&lt;h1&gt;
  
  
  How Debt is Calculated
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;It's hard to see at a glance how much you owe.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;To address the issue mentioned above, the app displays a summary of each person’s debt at the top of the screen. This allows you to see your outstanding balance at a glance when you open the app.&lt;/p&gt;

&lt;p&gt;The summary displays two items: &lt;code&gt;Total NET debt&lt;/code&gt; and &lt;code&gt;debt&lt;/code&gt;.Here’s what each of these represents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Total NET debt&lt;/code&gt; – This shows the final amount one person owes after subtracting mutual unpaid amounts.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;debt&lt;/code&gt; – This shows the total unpaid amount for each person before any offsets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s look at a specific example.&lt;/p&gt;

&lt;p&gt;In the image, the husband has already covered 2,500 yen, while the wife has covered 398 yen. The husband needs to pay the wife half of 398 yen, which is 199 yen. The wife needs to pay half of 2,500 yen, which is 1,250 yen. These two amounts are shown under &lt;code&gt;debt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Subtracting these two values gives 1,250 - 199 = 1,051, meaning the wife ultimately owes the husband 1,051 yen.&lt;/p&gt;

&lt;p&gt;Therefore, the &lt;code&gt;Total NET debt&lt;/code&gt; is 0 yen for the husband and 1,051 yen for the wife.&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;debt&lt;/th&gt;
&lt;th&gt;Total NET debt&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Husband&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;¥199&lt;/td&gt;
&lt;td&gt;¥0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Wife&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;¥1,250&lt;/td&gt;
&lt;td&gt;¥1,051&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h1&gt;
  
  
  App Architecture
&lt;/h1&gt;

&lt;p&gt;The architecture of the app is as follows. For those familiar with Amplify, this setup should be quite familiar.&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%2Fpu4sc09npd5l1pyrh8fl.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%2Fpu4sc09npd5l1pyrh8fl.png" alt="Screenshot 2024-08-18 11.03.24.png" width="800" height="570"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Hosting
&lt;/h3&gt;

&lt;p&gt;I use Amplify Hosting. Under the hood, S3 and CloudFront are used to deliver the HTML content.&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%2Fnhnk977umxzfzzaofpmw.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%2Fnhnk977umxzfzzaofpmw.png" alt="Screenshot 2024-08-18 11.06.08.png" width="800" height="277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The S3 bucket and CloudFront distribution used here cannot be viewed or edited. It seems that these resources are managed internally by AWS.&lt;/p&gt;
&lt;h3&gt;
  
  
  Database &amp;amp; API
&lt;/h3&gt;

&lt;p&gt;I use DynamoDB as the database, where I store the items each person has paid for. CRUD operations are performed using AppSync with GraphQL.&lt;/p&gt;

&lt;p&gt;In Amplify, a file called &lt;code&gt;amplify/data/resource.ts&lt;/code&gt; is automatically generated, where you can define the DynamoDB architecture as IaC (Infrastructure as Code).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;defineData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ClientSchema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@aws-amplify/backend&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;isDone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&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="nf"&gt;authorization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;allow&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)]),&lt;/span&gt; &lt;span class="c1"&gt;// User-group based data access&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Used for code completion / highlighting when making requests from frontend&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ClientSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// defines the data resource to be deployed&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineData&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;authorizationModes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;defaultAuthorizationMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apiKey&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;apiKeyAuthorizationMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;expiresInDays&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&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;As mentioned at the beginning, this app is only used by my wife and me, so proper access control for viewing and editing app data is essential. In &lt;code&gt;data/resource.ts&lt;/code&gt;, I’ve set up access controls so that only users in a specific user group on Cognito can access DynamoDB. The relevant part is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;isDone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&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="nf"&gt;authorization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;allow&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)]),&lt;/span&gt; &lt;span class="c1"&gt;// User-group based data access&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;.authorization(allow =&amp;gt; [allow.group('Admin')]),&lt;/code&gt; ensures that only users in the &lt;code&gt;Admin&lt;/code&gt; group have access. (The &lt;code&gt;Admin&lt;/code&gt; group is manually created in Cognito via the AWS Management Console beforehand.)&lt;/p&gt;

&lt;p&gt;References:&lt;br&gt;
&lt;a href="https://docs.amplify.aws/react/build-a-backend/data/set-up-data/" rel="noopener noreferrer"&gt;https://docs.amplify.aws/react/build-a-backend/data/set-up-data/&lt;/a&gt;&lt;br&gt;
&lt;a href="https://docs.amplify.aws/react/build-a-backend/data/customize-authz/user-group-based-data-access/" rel="noopener noreferrer"&gt;https://docs.amplify.aws/react/build-a-backend/data/customize-authz/user-group-based-data-access/&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Auth
&lt;/h3&gt;

&lt;p&gt;Cognito is used for authentication.&lt;/p&gt;

&lt;p&gt;Similar to the database, a file called &lt;code&gt;amplify/auth/resource.ts&lt;/code&gt; is automatically generated, where you can define the authentication architecture.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineAuth&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-amplify/backend&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Define and configure your auth resource
 * @see https://docs.amplify.aws/gen2/build-a-backend/auth
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineAuth&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;loginWith&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;References:&lt;br&gt;
&lt;a href="https://docs.amplify.aws/react/build-a-backend/auth/set-up-auth/" rel="noopener noreferrer"&gt;https://docs.amplify.aws/react/build-a-backend/auth/set-up-auth/&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Operating Costs
&lt;/h1&gt;

&lt;p&gt;Amplify is billed on a pay-as-you-go basis.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/jp/amplify/pricing/" rel="noopener noreferrer"&gt;https://aws.amazon.com/jp/amplify/pricing/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The operating cost of my household budgeting app is about 100 ~ 200 yen (≒ $1 ~ $1.5)per month. Since the active users are just my wife and me, it’s practically free.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Managing our budget with the app has become much easier compared to using a spreadsheet. The app still has some rough edges, and I occasionally find bugs, so I plan to maintain it at my own pace.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Journey to Creating and Publishing a Custom CLI Command in Go</title>
      <dc:creator>Toru Takahashi</dc:creator>
      <pubDate>Mon, 15 Jul 2024 02:27:46 +0000</pubDate>
      <link>https://forem.com/tttol/journey-to-creating-and-publishing-a-custom-cli-command-in-go-99o</link>
      <guid>https://forem.com/tttol/journey-to-creating-and-publishing-a-custom-cli-command-in-go-99o</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;💡Original japanese post is here.&lt;br&gt;
&lt;a href="https://zenn.dev/tttol/articles/c7dfc74d27e45d" rel="noopener noreferrer"&gt;https://zenn.dev/tttol/articles/c7dfc74d27e45d&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;I implemented a CLI command in Go and published it on GitHub.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tttol/mergectl" rel="noopener noreferrer"&gt;https://github.com/tttol/mergectl&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's called &lt;code&gt;mergectl&lt;/code&gt;. You can use &lt;code&gt;mergectl&lt;/code&gt; by downloading the binary with curl and saving it to /usr/local/bin. In this article, I’ll document the journey of implementing, publishing, and distributing a CLI command in Go as a reference.&lt;/p&gt;

&lt;h1&gt;
  
  
  Explanation of &lt;code&gt;mergectl&lt;/code&gt;'s Functionality and Development Background
&lt;/h1&gt;

&lt;p&gt;Before diving into the main topic, let me explain the functionality and development background of &lt;code&gt;mergectl&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Functionality
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;mergectl&lt;/code&gt; is a command to merge multiple git branches at once.&lt;/p&gt;

&lt;p&gt;For example, let's assume we have the following branch structure.&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%2Fvna2725vfg08aacut5dl.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%2Fvna2725vfg08aacut5dl.png" alt=" " width="800" height="1090"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In my work, we often develop multiple versions in parallel, cutting the v1 branch from the main branch and the v2 branch from the v1 branch.&lt;/p&gt;

&lt;p&gt;Any changes pushed to v1 need to be reflected in v2 as well. This means that we need to regularly merge v1 into v2. While this is a simple task of executing &lt;code&gt;git merge remotes/origin/v1&lt;/code&gt; on the v2 branch, the number of commits pushed to v1 and v2 by the development team makes manual merging cumbersome.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;mergectl&lt;/code&gt;, you can merge v1 into v2 by simply running &lt;code&gt;mergectl exec v1 v2&lt;/code&gt; in the directory where the git repository is cloned.&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%2Fzr2vwtmkagxq626cn3k0.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%2Fzr2vwtmkagxq626cn3k0.png" alt=" " width="800" height="1256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Similarly, if there are changes in the main branch due to a hotfix, you can reflect those changes in the v1 and v2 branches by running &lt;code&gt;mergectl exec main v1 v2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mergectl exec [source branch] [target branch 1] [target branch 2] [target branch 3] ...&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4s80zm4gx390nbq8z8o1.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%2F4s80zm4gx390nbq8z8o1.png" alt=" " width="800" height="841"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;mergectl&lt;/code&gt; was originally a logic that I ran with a shell script. I had set up the following shell script to run regularly on my PC.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# merge main into v1&lt;/span&gt;
git checkout v1
git pull
git merge remotes/origin/main &lt;span class="nt"&gt;--no-ff&lt;/span&gt;
git push

&lt;span class="c"&gt;# merge v1 into v2&lt;/span&gt;
git checkout v2
git pull
git merge remotes/origin/v1 &lt;span class="nt"&gt;--no-ff&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While this shell script was functioning well, it became cumbersome to change branch names within the script whenever a new version like v3 or v4 was introduced. Manually updating the script posed a risk of mixing up source and target branches.&lt;/p&gt;

&lt;p&gt;This is where the idea of creating a CLI command came from, to simplify these tasks.&lt;/p&gt;

&lt;h1&gt;
  
  
  Library for CLI Command Development - Cobra
&lt;/h1&gt;

&lt;p&gt;Now, let’s get into the main topic.&lt;/p&gt;

&lt;p&gt;One way to implement a CLI command in Go is to use a library called Cobra.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/spf13/cobra" rel="noopener noreferrer"&gt;https://github.com/spf13/cobra&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cobra is a library that supports and accelerates the implementation of CLI applications. For detailed usage, please refer to the documentation.&lt;/p&gt;

&lt;p&gt;Since Cobra is implemented in Go, you can easily install it with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get &lt;span class="nt"&gt;-u&lt;/span&gt; github.com/spf13/cobra@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Cobra, you can easily implement the &lt;code&gt;mergectl&lt;/code&gt; command as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Execute&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/spf13/cobra"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rootCmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cobra&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Use&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;"mergectl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Short&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"USAGE: mergectl [source branch] [target branch]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;`mergectl is a tool of "git merge". This command merges multiple git branches.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cobra&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello mergectl!"&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="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;rootCmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;mergectl
Hello mergectl!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this, we have created a CLI command that returns "Hello mergectl!" when executed.&lt;/p&gt;

&lt;p&gt;Additionally, you can implement subcommands as well. By implementing the following &lt;code&gt;version.go&lt;/code&gt;, running &lt;code&gt;mergectl version&lt;/code&gt; will return the version of the CLI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/spf13/cobra"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;Version&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;versionCmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cobra&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Use&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Short&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Print the version number of mergectl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cobra&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mergectl version:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Version&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="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;rootCmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;versionCmd&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mergectl version
1.0.0-rc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can add the subcommand defined with &lt;code&gt;var versionCmd&lt;/code&gt; to the root with &lt;code&gt;rootCmd.AddCommand(versionCmd)&lt;/code&gt;. How the value is assigned to &lt;code&gt;var Version string&lt;/code&gt; is explained in the next chapter.&lt;/p&gt;

&lt;h1&gt;
  
  
  Managing Application Version
&lt;/h1&gt;

&lt;p&gt;The version value is assigned to &lt;code&gt;var Version string&lt;/code&gt; in &lt;code&gt;version.go&lt;/code&gt;. The assignment is specified during the build process with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go build &lt;span class="nt"&gt;-ldflags&lt;/span&gt; &lt;span class="s2"&gt;"-X main.version=1.0.0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can receive the value of &lt;code&gt;main.version&lt;/code&gt; in &lt;code&gt;main.go&lt;/code&gt; as &lt;code&gt;var version&lt;/code&gt;. Once received in &lt;code&gt;main.go&lt;/code&gt;, you can pass this value to &lt;code&gt;var Version string&lt;/code&gt; in &lt;code&gt;version.go&lt;/code&gt; and display it appropriately.&lt;/p&gt;

&lt;h1&gt;
  
  
  Library for Release
&lt;/h1&gt;

&lt;p&gt;Next, I’ll explain the release process.&lt;/p&gt;

&lt;h2&gt;
  
  
  GoReleaser
&lt;/h2&gt;

&lt;p&gt;The created CLI command is distributed as a binary file. For creating the binary, we use GoReleaser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://goreleaser.com/" rel="noopener noreferrer"&gt;https://goreleaser.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For detailed usage, please refer to the documentation.&lt;/p&gt;

&lt;p&gt;GoReleaser manages release settings with a .goreleaser.yml file. You can automatically generate this yml file by running &lt;code&gt;goreleaser init&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In my case, I used the generated yml file with minimal modifications. The parts I modified are as follows:&lt;/p&gt;

&lt;h2&gt;
  
  
  Changes in .goreleaser.yml
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;builds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;ldflags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-s -w&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-X main.version={{ .Version }}&lt;/span&gt;
    &lt;span class="c1"&gt;# Omitted&lt;/span&gt;
  &lt;span class="na"&gt;flags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-trimpath&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-ldflags "-X main.version={{ .Version }}"&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;This option sets the application version as explained earlier.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;-s&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Strips debug information.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;-w&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Strips debug information.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Reference Link&lt;/p&gt;

&lt;p&gt;&lt;a href="https://qiita.com/ssc-ynakamura/items/da37856f7f217d708a07" rel="noopener noreferrer"&gt;https://qiita.com/ssc-ynakamura/items/da37856f7f217d708a07&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;The &lt;code&gt;mergectl&lt;/code&gt; I created is available as OSS, so feel free to use it according to the license.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Boost Your Development Efficiency! Simulate S3 with a Custom Amazon S3 Mock Application</title>
      <dc:creator>Toru Takahashi</dc:creator>
      <pubDate>Wed, 10 Jul 2024 23:23:16 +0000</pubDate>
      <link>https://forem.com/aws-builders/boost-your-development-efficiency-simulate-s3-with-a-custom-amazon-s3-mock-application-19ah</link>
      <guid>https://forem.com/aws-builders/boost-your-development-efficiency-simulate-s3-with-a-custom-amazon-s3-mock-application-19ah</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;💡Original japanese post is here.&lt;br&gt;
&lt;a href="https://zenn.dev/tttol/articles/13032ef69d8333" rel="noopener noreferrer"&gt;https://zenn.dev/tttol/articles/13032ef69d8333&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I've developed a mock application for Amazon S3 called "MOS3" (pronounced "mɒsˈθri"). Similar to &lt;a href="https://www.localstack.cloud/" rel="noopener noreferrer"&gt;LocalStack&lt;/a&gt;, MOS3 is an S3-like application that runs in a local environment.&lt;/p&gt;

&lt;p&gt;MOS3 is a GUI application that allows you to manage files directly from your browser. You can upload files without having to use CLI commands like &lt;code&gt;aws s3 cp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff6ngdsa3folkkcip6jeq.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%2Ff6ngdsa3folkkcip6jeq.png" alt="mos3" width="800" height="263"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Moreover, MOS3 can handle S3 requests from the AWS SDK, making it possible to simulate S3 operations locally.&lt;/p&gt;

&lt;p&gt;You can find the source code here:&lt;br&gt;
&lt;a href="https://github.com/tttol/mos3" rel="noopener noreferrer"&gt;https://github.com/tttol/mos3&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;I am an application engineer, and I frequently develop web applications using Java and other technologies.&lt;/p&gt;

&lt;p&gt;Many of the applications I create use S3 as external storage, and it's often necessary to connect to S3 even for local development. While I sometimes use a development AWS account with actual S3 buckets, setting up an account can be cumbersome for small-scale applications. In such cases, I've used LocalStack as a mock for S3.&lt;/p&gt;

&lt;p&gt;LocalStack is an excellent and convenient open-source software, but I have noticed some challenges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's primarily operated via CLI, making GUI-based file management difficult.

&lt;ul&gt;
&lt;li&gt;There is a GUI feature, but it didn't meet my requirements.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;LocalStack provides resources beyond S3, but I rarely need anything other than S3, making it overly complex for my needs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Given these challenges, I thought, "Why not create something that perfectly fits my needs?" And thus, MOS3 was born.&lt;/p&gt;
&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Use as a Local Replacement for S3
&lt;/h3&gt;

&lt;p&gt;MOS3 runs at &lt;a href="http://localhost:33333/s3" rel="noopener noreferrer"&gt;http://localhost:33333/s3&lt;/a&gt;.&lt;br&gt;
※ For detailed installation instructions, please refer to the &lt;a href="https://github.com/tttol/mos3?tab=readme-ov-file#install" rel="noopener noreferrer"&gt;README&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;MOS3 can be used as a standalone S3-like local file server, but it also accepts requests from the AWS SDK. For example, if you send a request using the &lt;a href="https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/s3/AmazonS3Client.html#getObject-com.amazonaws.services.s3.model.GetObjectRequest-" rel="noopener noreferrer"&gt;getObjects&lt;/a&gt; method from the AWS SDK for Java, MOS3 will respond with the specified key's object.&lt;/p&gt;

&lt;p&gt;To call MOS3 from the AWS SDK for Java, you need to set the endpoint of the &lt;code&gt;S3Client&lt;/code&gt; to &lt;a href="http://localhost:3333" rel="noopener noreferrer"&gt;http://localhost:3333&lt;/a&gt;. Here’s a code sample:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;S3Client&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;S3Client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;endpointOverride&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:3333"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Caution
&lt;/h4&gt;

&lt;p&gt;When specifying localhost as the endpoint, you may need to enable path-style access for it to work correctly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;S3Configuration&lt;/span&gt; &lt;span class="n"&gt;s3Configuration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;S3Configuration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pathStyleAccessEnabled&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// enable path style access&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="nc"&gt;S3Client&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;S3Client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;endpointOverride&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:3333"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;serviceConfiguration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s3Configuration&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  File Management via Browser
&lt;/h3&gt;

&lt;p&gt;MOS3 allows you to manage files and directories through your browser.&lt;/p&gt;

&lt;p&gt;You can upload files by clicking the "New File" button.&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%2Fjmtme0wmgu8v8yd7w8jl.gif" 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%2Fjmtme0wmgu8v8yd7w8jl.gif" alt="newfile.gif" width="360" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can create new directories by clicking the "New Dir" button.&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%2F02u15333we6i0b6qk21t.gif" 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%2F02u15333we6i0b6qk21t.gif" alt="nerdir.gif" width="360" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can download files by clicking on them.&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%2Fzj41j0m0ddg57d697vxm.gif" 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%2Fzj41j0m0ddg57d697vxm.gif" alt="download.gif" width="360" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can delete files or directories by clicking the trash can icon on the right.&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%2Fe1guzv2vdiqhywdcdoz3.gif" 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%2Fe1guzv2vdiqhywdcdoz3.gif" alt="remove.gif" width="360" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Linking with Your Local PC Directory
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 3333:3333 &lt;span class="nt"&gt;-v&lt;/span&gt; ./upload:/app/upload &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; tttol/mos3:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above is the command to run MOS3. (Quoted from the &lt;a href="https://github.com/tttol/mos3?tab=readme-ov-file#install" rel="noopener noreferrer"&gt;README&lt;/a&gt;)&lt;br&gt;
MOS3 runs on a Docker container.&lt;/p&gt;

&lt;p&gt;The key point here is the use of the -v option to mount the volume. Files uploaded to MOS3 are internally stored in the &lt;code&gt;/app/upload&lt;/code&gt; directory. By mounting it with &lt;code&gt;-v ./upload:/app/upload&lt;/code&gt; during container startup, files on MOS3 are synchronized with the &lt;code&gt;./upload&lt;/code&gt; directory. Therefore, you can manage MOS3 files directly from Mac Finder or Windows Explorer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Future Features
&lt;/h2&gt;

&lt;p&gt;MOS3 is still a work in progress. Below are some of the features I plan to implement in the future.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling Requests from AWS SDKs Other Than Java
&lt;/h3&gt;

&lt;p&gt;I often use Java, so I have mainly implemented support for requests from the AWS SDK for Java. However, the behavior with other SDKs is not guaranteed, as some work and others do not. I plan to extend support to other SDKs in the future, but this remains a future task.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling AWS CLI Requests
&lt;/h3&gt;

&lt;p&gt;Handling AWS CLI requests for commands like ls, mb, cp, and rm is still a work in progress. While ls and cp are minimally supported, handling other commands is a future task.&lt;/p&gt;

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

&lt;p&gt;MOS3 is open-source software (MIT License). Issues and pull requests are welcome.&lt;br&gt;
I will continue to improve MOS3 to make it an even more user-friendly application.&lt;/p&gt;

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