ちばのてっく

積極的にアウトプット

AWS CLIで最新の無料利用対象AMI IDを出力してみた

TerraformでEC2インスタンスを作成しようとしたときに、AMI IDが必要となる。EC2コンソールから確認する方法はタイプミスも起こりうるし手間なので、AWS CLIを使って最新かつ無料利用対象のAMIを取得してみる。

はじめに結論

入力

aws ec2 describe-images --owners amazon --filters 'Name=name,Values=al2023-ami-2*' --query 'sort_by(Images,&CreationDate)[-1].[ImageId]' --output text

出力

ami-034c9ca2bdde7b472

やってみた

準備運動

AWS CLIのリファレンスを参考にいろいろ試してみる。

まずは、オプションなしで実行してみる。

aws ec2 describe-images

json形式で情報が出力された。なお、ページネーションされており、出力された情報はここに転記した限りのものではない。なお、全件取得しているから、出力されるまでにちょい時間もかかった。

{
    "Images": [
        {
            "Architecture": "arm64",
            "CreationDate": "2022-10-26T08:50:11.000Z",
            "ImageId": "ami-0783825db772157e8",
            "ImageLocation": "aws-marketplace/ARM_Docs_Enterprise_Edition_100-322bee60-8ff7-4367-bce6-0c3d414e7c2f",
            "ImageType": "machine",
            "Public": true,
            "OwnerId": "679593333241",
            "PlatformDetails": "Linux/UNIX",
            "UsageOperation": "RunInstances",
            "ProductCodes": [
                {
                    "ProductCodeId": "2yxhenxqx8xwfnu96mclbi2i7",
                    "ProductCodeType": "marketplace"
                }
            ],
            "State": "available",
            "BlockDeviceMappings": [
                {
                    "DeviceName": "/dev/sda1",
                    "Ebs": {
                        "DeleteOnTermination": true,
                        "SnapshotId": "snap-022658c8e17e00925",
                        "VolumeSize": 50,
                        "VolumeType": "gp2",
                        "Encrypted": false
                    }
                },
:

次は、AWS公式ドキュメントにあるサンプル通りに実行してみる。

aws ec2 describe-images --owners self amazon

--owners self amazonを追加したため、自身およびAWSが所有するAMIの情報が出力された。

{
    "Images": [
        {
            "Architecture": "arm64",
            "CreationDate": "2022-09-11T18:43:34.000Z",
            "ImageId": "ami-072fd2ac175987855",
            "ImageLocation": "amazon/debian-11-arm64-20220911-1135",
            "ImageType": "machine",
            "Public": true,
            "OwnerId": "136693071363",
            "PlatformDetails": "Linux/UNIX",
            "UsageOperation": "RunInstances",
            "State": "available",
            "BlockDeviceMappings": [
                {
                    "DeviceName": "/dev/xvda",
                    "Ebs": {
                        "DeleteOnTermination": true,
                        "SnapshotId": "snap-049f8f94068975a38",
                        "VolumeSize": 8,
                        "VolumeType": "gp2",
                        "Encrypted": false
                    }
                }
            ],
            "Description": "Debian 11 (20220911-1135)",
            "EnaSupport": true,
            "Hypervisor": "xen",
            "ImageOwnerAlias": "amazon",
            "Name": "debian-11-arm64-20220911-1135",
            "RootDeviceName": "/dev/xvda",
:

ようやく本題

--query

JSON形式でずらっと表示する必要はなく、AMI IDだけがわかればよいため、--queryオプションを使って出力結果を絞る。直前の出力結果から、Images配下のImageIdにAMI IDが格納されていることがわかったため、以下のようなコマンドを発行する。

aws ec2 describe-images --owners amazon --query 'Images[].[ImageId]'

AMI IDだけが出力されるようになった。

[
    [
        "ami-07f84aef74d43e3f6"
    ],
    [
        "ami-077b3ac4f90ca037d"
    ],
    [
        "ami-0c3be11bbde86acc9"
    ],
    [
        "ami-0536b4ea4c52f3de7"
    ],
    [
        "ami-00f135819964ee6d4"
:

JSON形式だと見づらいため、--outputオプションを用いてtext形式で出力されるようにする。

aws ec2 describe-images --owners amazon --query 'Images[].[ImageId]' --output text

text形式になり、みやすくなった。

ami-096260c2e6846d8cc
ami-03d1ed1b9fd997201
ami-0abede41b16d742a4
ami-0eb19f14e6cd47620
ami-06e2e2f7191df1cff
ami-0e0b03b0858b9c598
ami-07259a121c2c3b64e
ami-0deb2dde49a4fbfc5
ami-01f8e82710fa38050
ami-089caded1bdbfbe5c
ami-066623c10b1002216
ami-0b2a4d3ca2a8dee16
ami-0d49ed3d0db984485
ami-0725760eac0602582
ami-019501f824222b0ce
:

最新のAMI IDのみ出力すればよいため、--queryオプションをさらに修正する。 クエリではJMESPathなる、JSONのクエリ言語を扱える。

クエリでは、JMESPath 構文を使用して、出力をフィルタリングするための式を作成します

引用:AWS CLI の出力をフィルタリングする https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-usage-filter.html#cli-usage-filter-client-side

まずは、JMESPathの組み込み関数であるsort_byを利用して、出力結果をソートする。ソートキーには、CreationDateを使うこととする。なお、sort_byは昇順ソートのため、末尾のAMI IDを取得する。

aws ec2 describe-images --owners amazon --query 'sort_by(Images,&CreationDate)[-1].[ImageId]' --output text

あるいはJMESPathの組み込み関数であるreverseを組み合わせて、降順にソートしたうえで、先頭のデータを取得する。

aws ec2 describe-images --owners amazon --query 'reverse(sort_by(Images,&CreationDate))[0].[ImageId]' --output text

すると、最新の1件のAMI IDが取得できた。

ami-08667f7205cd9a3f0
--filters

次に、--filtersオプションで、AmazonLinux2023が出力されるようにフィルタリングする。なお、AmazonLinux2023のAMI IDは、al2023-ami-で始まることをEC2コンソールから確認した。

なお、--filtersは、Name=hoge,Values=fugaを指定する。Nameの値はAWS CLI Command Referenceを参照。また、Valuesの値には*や?といったワイルドカードが使える。

aws ec2 describe-images --owners amazon --filters 'Name=name,Values=al2023-ami-*' --query 'sort_by(Images,&CreationDate)[-1].[ImageId]' --output text

お目当てのAMI IDを取得できたと思ったが、よくみるとEC2コンソールで確認したAMI IDと違った。

ami-092ee79bf91da299e

そこで、それぞれのAMIの情報を比較してみた。

aws ec2 describe-images --owners amazon --filters 'Name=image-id,Values=ami-092ee79bf91da299e' > ami-092ee79bf91da299e.txt
aws ec2 describe-images --owners amazon --filters 'Name=image-id,Values=ami-034c9ca2bdde7b472' > ami-034c9ca2bdde7b472.txt
diff ami-092ee79bf91da299e.txt ami-034c9ca2bdde7b472.txt

すると、Nameに有意な差がみられた。EC2コンソールで確認した無料利用対象のNameには、minimalという文字列が含まれていなかった。

5,7c5,7
<             "CreationDate": "2024-03-13T02:25:01.000Z",
<             "ImageId": "ami-092ee79bf91da299e",
<             "ImageLocation": "amazon/al2023-ami-minimal-2023.3.20240312.0-kernel-6.1-x86_64",
---
>             "CreationDate": "2024-03-13T02:16:23.000Z",
>             "ImageId": "ami-034c9ca2bdde7b472",
>             "ImageLocation": "amazon/al2023-ami-2023.3.20240312.0-kernel-6.1-x86_64",
20c20
<                         "SnapshotId": "snap-0b64bb669a51ba903",
---
>                         "SnapshotId": "snap-0a53ae1296688c3dc",
28c28
<             "Description": "Amazon Linux 2023 AMI 2023.3.20240312.0 x86_64 Minimal HVM kernel-6.1",
---
>             "Description": "Amazon Linux 2023 AMI 2023.3.20240312.0 x86_64 HVM kernel-6.1",
32c32
<             "Name": "al2023-ami-minimal-2023.3.20240312.0-kernel-6.1-x86_64",
---
>             "Name": "al2023-ami-2023.3.20240312.0-kernel-6.1-x86_64",
38c38
<             "DeprecationTime": "2024-06-11T02:25:00.000Z",
---
>             "DeprecationTime": "2024-06-11T02:16:00.000Z",

ので、--filtersの引数を修正した。

aws ec2 describe-images --owners amazon --filters 'Name=name,Values=al2023-ami-2*' --query 'sort_by(Images,&CreationDate)[-1].[ImageId]' --output text

無事、無料利用対象の最新AMI IDを取得できた。

ami-034c9ca2bdde7b472

残課題

今回のように近しい命名規則のAMIが追加された場合や、AWS側でNameの命名規則を変えた場合に、期待通りの出力結果が得られなくなる可能性がある。無料利用対象という条件で絞り込むような、スマートな方法があるのかもしれない。

FlaskをLambda+API Gatewayでホスティングしてみた

Flaskをローカルではなくクラウド上にホスティングする方法を知っておきたい。 ということで、手始めにLambda + API Gatewayホスティングしてみる。

やってみた

Lambda + API Gatewayということで、AWS SAMを使うのが手っ取り早そう。sam initでテンプレートを取得して、それをカスタマイズする方針で進める。

sam init

ウィザードには以下の通りに回答していく。

You can preselect a particular runtime or package type when using the `sam init` experience.
Call `sam init --help` to learn more.

# 用意されたテンプレートを使いたいため、1
Which template source would you like to use?
        1 - AWS Quick Start Templates
        2 - Custom Template Location
Choice: 1

# `Hello World Example`が最も簡易な構造な気がしたため、1
Choose an AWS Quick Start application template
        1 - Hello World Example
        2 - Data processing
        3 - Hello World Example with Powertools for AWS Lambda
        4 - Multi-step workflow
        5 - Scheduled task
        6 - Standalone function
        7 - Serverless API
        8 - Infrastructure event management
        9 - Lambda Response Streaming
        10 - Serverless Connector Hello World Example
        11 - Multi-step workflow with Connectors
        12 - GraphQLApi Hello World Example
        13 - Full Stack
        14 - Lambda EFS example
        15 - Hello World Example With Powertools for AWS Lambda
        16 - DynamoDB Example
        17 - Machine Learning
Template: 1

# ランタイムにPythonを選択したかったため、また、パッケージはコンテナイメージではなくZipとするため、y
Use the most popular runtime and package type? (Python and zip) [y/N]: y

# X-Rayは必要ないため、n
Would you like to enable X-Ray tracing on the function(s) in your application?  [y/N]: n

# CloudWatch Application Insightsは必要ないため、y
Would you like to enable monitoring using CloudWatch Application Insights?
For more info, please view https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch-application-insights.html [y/N]: n

# ログをJSON型式で出力しなくてもよいため、n
Would you like to set Structured Logging in JSON format on your Lambda functions?  [y/N]: n

# プロジェクト名はデフォルト(sam-app)で問題ないため、何も入力せずEnter
Project name [sam-app]:

    -----------------------
    Generating application:
    -----------------------
    Name: sam-app
    Runtime: python3.9
    Architectures: x86_64
    Dependency Manager: pip
    Application Template: hello-world
    Output Directory: .
    Configuration file: sam-app/samconfig.toml

    Next steps can be found in the README file at sam-app/README.md


Commands you can use next
=========================
[*] Create pipeline: cd sam-app && sam pipeline init --bootstrap
[*] Validate SAM template: cd sam-app && sam validate
[*] Test Function in the Cloud: cd sam-app && sam sync --stack-name {stack-name} --watch

すると、以下のディレクトリ構造がつくられる。これらをベースに、以下のようにカスタマイズしていく。

  • template.yamlの修正
  • layer/requirements.txtの新規追加
  • app.pyの修正
.
└── sam-app
    ├── README.md
    ├── __init__.py
    ├── events
    │   └── event.json
    ├── hello_world
    │   ├── __init__.py
    │   ├── app.py
    │   └── requirements.txt
    ├── samconfig.toml
    ├── template.yaml
    └── tests
        ├── __init__.py
        ├── integration
        │   ├── __init__.py
        │   └── test_api_gateway.py
        ├── requirements.txt
        └── unit
            ├── __init__.py
            └── test_handler.py

template.yamlの修正

主な修正点

  • Flask側でパスルーティングを行うために、すべてのサブリソースに対するリクエストを処理するプロキシリソースを追加。(慣例的にproxy+としたが、任意の文字列にプラス記号がついたものであればOKらしい。)

  • 外部ライブラリをLambdaレイヤーにまとめておくために、Type: AWS::Serverless::LayerVersionのFlaskAppLayer句を追加。

  • AWSLambdaBasicExecutionRoleというCloudWatchへログを出力する基本的な権限+DynamoDBへアクセスするための権限を与えるために、Policies句を追加。

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: flask-app

Globals:
  Function:
    Timeout: 3
    MemorySize: 128

Resources:
  FlaskAppFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.12
      Architectures:
        - x86_64
      Layers: 
        - !Ref FlaskAppLayer
      Events:
        Root:
          Type: Api
          Properties:
            Path: /
            Method: ANY
        Al:
          Type: Api
          Properties:
            Path: /{proxy+}
            Method: ANY
      Policies:
        - AWSLambdaBasicExecutionRole
        - Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action:
                - dynamodb:Scan
                - dynamodb:PutItem
              Resource: 
                - !Sub 'arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/temp'
  FlaskAppLayer:
    Type: AWS::Serverless::LayerVersion
    Properties:
      CompatibleRuntimes:
        - python3.12
      ContentUri: layer/
      LayerName: flask-app
    Metadata:
      BuildMethod: python3.12

layer/requirements.txtの新規追加

template.yamlContentUri: layer/と定義したため、template.yamlと同階層にlayerディレクトリを作成し、その中にrequirementes.txtを作成する。なお、requirementes.txtに記載した外部ライブラリは自動で取得され、Lambdaレイヤーが作成される。

requirementes.txt

aws-wsgi
flask

app.pyの修正

Hello worldのような文字列の返却だけではなんとなくさみしいため、AWSリソースへもアクセスさせてみたい。 そこで、先日の記事を参考に、dynamoDBテーブルへscanを行う処理を記載する。

また、Flaskを用いて、LambdaとAPI Gateway間をWSGIでやり取りをするために、AWSGIを利用する。そのため、lambda_handlerのreturnにawsgi.response(app, event, context)を設定する。 (AWSGIもWSGIもよくわかってはいない、、)

app.py

from flask import Flask
import awsgi
import boto3

app = Flask(__name__)

@app.route("/")
def hello_world():
    return "<p>Hello world!</p>"

@app.route("/dynamodb_scan/")
def dynamodb_scan():
    client = boto3.client("dynamodb")
    response = client.scan(
        TableName="temp"
    )
    return response

def lambda_handler(event, context):
    return awsgi.response(app, event, context)  

3点のカスタマイズが完了したら、ビルド、デプロイを行う。

sam build --use-container
sam deploy --guided

ウィザードには以下の通りに回答していく。

Configuring SAM deploy
======================

        Looking for config file [samconfig.toml] :  Found
        Reading default arguments  :  Success

        Setting default arguments for 'sam deploy'
        =========================================
        Stack Name [sam-app]:
        AWS Region [ap-northeast-1]: 
        #Shows you resources changes to be deployed and require a 'Y' to initiate deploy
        Confirm changes before deploy [Y/n]:
        #SAM needs permission to be able to create roles to connect to the resources in your template
        Allow SAM CLI IAM role creation [Y/n]:
        #Preserves the state of previously provisioned resources when an operation fails
        Disable rollback [y/N]:
        FlaskAppFunction has no authentication. Is this okay? [y/N]: y
        FlaskAppFunction has no authentication. Is this okay? [y/N]: y
        Save arguments to configuration file [Y/n]: 
        SAM configuration file [samconfig.toml]: 
        SAM configuration environment [default]: 

Lambdaコンソール > アプリケーション > sam-app から確認できるAPIエンドポイントへ、ブラウザからアクセスしたところ、期待通りHello world!が表示された。
また、<確認したAPIエンドポイント>/dynamodb_scanにアクセスしたところ、期待通りDynamoDBテーブルのscan結果が表示された。

Boto3でDynamoDBに、上書きを防ぎながらデータを追加してみた

昨日の記事の続き。
既存データの上書きを防ぎながら、put_itemでデータを追加してみる。

上書きを防ぐためには、ConditionExpressionattribute_not_exists(プライマリキー)を設定する。 すると、プライマリキーに重複するItemが存在する場合はput_itemが行われず、ConditionalCheckFailedExceptionを返す。

やってみた

ConditionExpression = "attribute_not_exists(target)"を設定して、プライマリキーが重複するようにput_itemで更新をかけてみる。

from flask import Flask,request
import boto3 

app = Flask(__name__)

@app.route("/")
def hello_world():
    return "<p>Hello world!</p>"

@app.route("/dynamodb_scan/")
def dynamodb_scan():
    client = boto3.client("dynamodb")
    response = client.scan(
        TableName="temp"
    )
    return response

@app.route("/dynamodb_put/")
def dynamodb_put():
    client = boto3.client("dynamodb")
    response  = client.put_item(
        TableName="temp",
        Item = {
            "target": { # pk
                "S": "pineapple",
            },
            "color": {
                "S": "yellow",
            }
        }
    )
    return response

@app.route("/dynamodb_put2/")
def dynamodb_put2():
    client = boto3.client("dynamodb")
    response  = client.put_item(
        TableName="temp",
        Item = {
            "target": { # pk
                "S": "pineapple",
            },
            "color": {
                "S": "green",
            }
        }
    )
    return response

# ↓今回追加した箇所
@app.route("/dynamodb_put3/")
def dynamodb_put3():
    client = boto3.client("dynamodb")
    response  = client.put_item(
        TableName="temp",
        Item = {
            "target": { # pk
                "S": "pineapple", # 既存Item
            },
            "color": {
                "S": "blue",
            }
        },
        ConditionExpression = "attribute_not_exists(target)"
    )
    return response
# ↑今回追加した箇所

if __name__ == "__main__":
    app.run(debug=True)

ブラウザでhttp://127.0.0.1:5000/dynamodb_put3/へアクセスすると、ConditionalCheckFailedExceptionと表示された。
scanでテーブルの中身を確認してみると、期待通りpineappleのcolorが更新されていないことが確認できた。

{
  "Count": 3,
  "Items": [
    {
      "color": {
        "S": "yellow"
      },
      "target": {
        "S": "banana"
      }
    },
    {
      "color": {
        "S": "red"
      },
      "target": {
        "S": "apple"
      }
    },
    {
      "color": {
        "S": "green"
      },
      "target": {
        "S": "pineapple"
      }
    }
  ],
  "ResponseMetadata": {
    "HTTPHeaders": {
      "connection": "keep-alive",
      "content-length": "183",
      "content-type": "application/x-amz-json-1.0",
      "date": "Sat, 16 Mar 2024 12:52:47 GMT",
      "server": "Server",
      "x-amz-crc32": "3681714569",
      "x-amzn-requestid": "LIL71PUF8I73CS091U1PQD4C37VV4KQNSO5AEMVJF66Q9ASUAAJG"
    },
    "HTTPStatusCode": 200,
    "RequestId": "LIL71PUF8I73CS091U1PQD4C37VV4KQNSO5AEMVJF66Q9ASUAAJG",
    "RetryAttempts": 0
  },
  "ScannedCount": 3
}

参考

条件付き配置

比較演算子および関数リファレンス

Boto3でDynamoDBにデータを追加してみた

Boto3でDynamoDBをScanしてみたの続き。
DyanmoDBにデータを追加してみる。

やってみた

put_itemでデータを追加してみる。
put_itemは、プライマリキーで追加するレコードを指定し、存在しないプライマリキーであれば新規追加、存在するプライマリキーであれば更新が行える。

新規追加

存在しないプライマリキーでput_itemしてみる。

from flask import Flask
import boto3 

app = Flask(__name__)

@app.route("/")
def hello_world():
    return "<p>Hello world!</p>"

@app.route("/dynamodb_scan/")
def dynamodb_scan():
    client = boto3.client("dynamodb")
    response = client.scan(
        TableName="temp"
    )
    return response

# ↓今回追加した箇所
@app.route("/dynamodb_put/")
def dynamodb_put():
    client = boto3.client("dynamodb")
    response  = client.put_item(
        TableName="temp",
        Item = {
            "target": { # pk
                "S": "pineapple",
            },
            "color": {
                "S": "yellow",
            }
        }
    )
    return response
# ↑今回追加した箇所

if __name__ == "__main__":
    app.run(debug=True)

無事、HTTPStatusCode200が返却された。

{
  "ResponseMetadata": {
    "HTTPHeaders": {
      "connection": "keep-alive",
      "content-length": "2",
      "content-type": "application/x-amz-json-1.0",
      "date": "Fri, 15 Mar 2024 01:38:06 GMT",
      "server": "Server",
      "x-amz-crc32": "2745614147",
      "x-amzn-requestid": "KKT5O9UGS4H6RQUVH82H7PMSCFVV4KQNSO5AEMVJF66Q9ASUAAJG"
    },
    "HTTPStatusCode": 200,
    "RequestId": "KKT5O9UGS4H6RQUVH82H7PMSCFVV4KQNSO5AEMVJF66Q9ASUAAJG",
    "RetryAttempts": 0
  }
}

追加されたItemをscanで確認してみる。putしたItemが追加されている。

{
  "Count": 3,
  "Items": [
    {
      "color": {
        "S": "yellow"
      },
      "target": {
        "S": "banana"
      }
    },
    {
      "color": {
        "S": "red"
      },
      "target": {
        "S": "apple"
      }
    },
    {
      "color": {
        "S": "yellow"
      },
      "target": {
        "S": "pineapple"
      }
    }
  ],
  "ResponseMetadata": {
    "HTTPHeaders": {
      "connection": "keep-alive",
      "content-length": "184",
      "content-type": "application/x-amz-json-1.0",
      "date": "Fri, 15 Mar 2024 01:40:35 GMT",
      "server": "Server",
      "x-amz-crc32": "2896593655",
      "x-amzn-requestid": "IMTUU8I25U2R8UDOF2NP7MO93BVV4KQNSO5AEMVJF66Q9ASUAAJG"
    },
    "HTTPStatusCode": 200,
    "RequestId": "IMTUU8I25U2R8UDOF2NP7MO93BVV4KQNSO5AEMVJF66Q9ASUAAJG",
    "RetryAttempts": 0
  },
  "ScannedCount": 3
}

更新

存在するプライマリキーでput_itemしてみる。

from flask import Flask
import boto3 

app = Flask(__name__)

@app.route("/")
def hello_world():
    return "<p>Hello world!</p>"

@app.route("/dynamodb_scan/")
def dynamodb_scan():
    client = boto3.client("dynamodb")
    response = client.scan(
        TableName="temp"
    )
    return response

@app.route("/dynamodb_put/")
def dynamodb_put():
    client = boto3.client("dynamodb")
    response  = client.put_item(
        TableName="temp",
        Item = {
            "target": { # pk
                "S": "pineapple",
            },
            "color": {
                "S": "yellow",
            }
        }
    )
    return response

# ↓今回追加した箇所
@app.route("/dynamodb_put2/")
def dynamodb_put2():
    client = boto3.client("dynamodb")
    response  = client.put_item(
        TableName="temp",
        Item = {
            "target": { # pk
                "S": "pineapple",
            },
            "color": {
                "S": "green",
            }
        }
    )
    return response
# ↑今回追加した箇所

if __name__ == "__main__":
    app.run(debug=True)

無事、HTTPStatusCode200が返却された。

{
  "ResponseMetadata": {
    "HTTPHeaders": {
      "connection": "keep-alive",
      "content-length": "2",
      "content-type": "application/x-amz-json-1.0",
      "date": "Fri, 15 Mar 2024 01:48:45 GMT",
      "server": "Server",
      "x-amz-crc32": "2745614147",
      "x-amzn-requestid": "MNNPDNMIGF1OGVDH3MMMOKUHHVVV4KQNSO5AEMVJF66Q9ASUAAJG"
    },
    "HTTPStatusCode": 200,
    "RequestId": "MNNPDNMIGF1OGVDH3MMMOKUHHVVV4KQNSO5AEMVJF66Q9ASUAAJG",
    "RetryAttempts": 0
  }
}

追加されたItemをscanで確認してみる。pineappleのcolorがgreenに更新されている。

{
  "Count": 3,
  "Items": [
    {
      "color": {
        "S": "yellow"
      },
      "target": {
        "S": "banana"
      }
    },
    {
      "color": {
        "S": "red"
      },
      "target": {
        "S": "apple"
      }
    },
    {
      "color": {
        "S": "green"
      },
      "target": {
        "S": "pineapple"
      }
    }
  ],
  "ResponseMetadata": {
    "HTTPHeaders": {
      "connection": "keep-alive",
      "content-length": "183",
      "content-type": "application/x-amz-json-1.0",
      "date": "Fri, 15 Mar 2024 01:49:44 GMT",
      "server": "Server",
      "x-amz-crc32": "3681714569",
      "x-amzn-requestid": "L3ECDIJLLETKV6R6JNNRRT8OERVV4KQNSO5AEMVJF66Q9ASUAAJG"
    },
    "HTTPStatusCode": 200,
    "RequestId": "L3ECDIJLLETKV6R6JNNRRT8OERVV4KQNSO5AEMVJF66Q9ASUAAJG",
    "RetryAttempts": 0
  },
  "ScannedCount": 3
}

参考

https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb/client/put_item.html

Boto3でDynamoDBをScanしてみた

昨日の記事の続き。 Flaskは動かせるようになったため、AWSリソースへアクセスしてみる。

やってみた

boto3のリファレンス を参考に、以下を作成。

from flask import Flask
import boto3 

app = Flask(__name__)

@app.route("/")
def hello_world():
    return "<p>Hello world!</p>"

@app.route("/dynamodb_scan/")
def dynamodb_scan():
    client = boto3.client("dynamodb")
    response = client.scan(
        TableName="temp"
    )
    return response


if __name__ == "__main__":
    app.run(debug=True)

Flaskを起動して、http://127.0.0.1:5000/dynamodb_scan/へブラウザからアクセスすると、Scan結果が得られた。

Scan結果

{
  "Count": 2,
  "Items": [
    {
      "color": {
        "S": "yellow"
      },
      "target": {
        "S": "banana"
      }
    },
    {
      "color": {
        "S": "red"
      },
      "target": {
        "S": "apple"
      }
    }
  ],
  "ResponseMetadata": {
    "HTTPHeaders": {
      "connection": "keep-alive",
      "content-length": "132",
      "content-type": "application/x-amz-json-1.0",
      "date": "Wed, 13 Mar 2024 22:29:09 GMT",
      "server": "Server",
      "x-amz-crc32": "2357452850",
      "x-amzn-requestid": "9E048FPDU66OTKVD4QQP98LSBRVV4KQNSO5AEMVJF66Q9ASUAAJG"
    },
    "HTTPStatusCode": 200,
    "RequestId": "9E048FPDU66OTKVD4QQP98LSBRVV4KQNSO5AEMVJF66Q9ASUAAJG",
    "RetryAttempts": 0
  },
  "ScannedCount": 2
}

memo

resourceは非推奨のため、clientを利用する。

https://boto3.amazonaws.com/v1/documentation/api/latest/guide/resources.html

flask runではなく、python ファイル名で実行すると404エラー

発生した問題

とほほのFlask入門チュートリアルを参考に、if __name__ほげほげブロックをapp = Flask(__name__)の直後に追記し、

from flask import Flask

app = Flask(__name__)

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)

pythonコマンドで実行した。(flask runコマンドではなく)

python hello.py

正常にFlaskが動作していることをターミナルで確認した後に、http://127.0.0.1:5000へブラウザからアクセスすると、404エラーが出力された。

127.0.0.1 - - [13/Mar/2024 21:33:12] "GET / HTTP/1.1" 404 -

解決方法

if __name__ほげほげブロックを、@app.route("/")ブロックの前ではなく、後に追記することで解決した。

OK

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
    return "<p>Hello world!</p>"

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)

NG

from flask import Flask

app = Flask(__name__)

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)

@app.route("/")
def hello_world():
    return "<p>Hello world!</p>"