LoginSignup
3
3

More than 5 years have passed since last update.

[JAWS-UG CLI] API Gateway: #3 RestAPIの作成(slack-echo-command)

Posted at

前提条件

API Gatewayへの権限

API Gatewayに対してフル権限があること。

Lambdaへの権限

Lambdaに対してフル権限があること。

AWS CLI

以下のバージョンで動作確認済

  • AWS CLI 1.10.58
コマンド
aws --version
結果(例)

      aws-cli/1.10.58 Python/2.7.11 Darwin/15.6.0 botocore/1.4.48

0. 準備

0.1. リージョンの決定

変数の設定
export AWS_DEFAULT_REGION='ap-northeast-1'

0.2. 変数の確認

プロファイルが想定のものになっていることを確認します。

変数の確認
aws configure list
結果(例)

            Name                    Value             Type    Location
            ----                    -----             ----    --------
         profile       lambdaFull-prjz-mbp13        env    AWS_DEFAULT_PROFILE
      access_key     ****************XXXX shared-credentials-file
      secret_key     ****************XXXX shared-credentials-file
          region        ap-northeast-1        env    AWS_DEFAULT_REGION

1. 事前作業

1.1. API名の指定

変数の設定
APIGW_API_NAME="slack-echo-command-$( date '+%Y%m%d' )" \
        && echo ${APIGW_API_NAME}

同名のREST APIが存在しないことを確認します。

コマンド
aws apigateway get-rest-apis \
        --query "items[?name == \`${APIGW_API_NAME}\`].name"
結果(例)

      []

1.2. AWS IDの取得

コマンド
AWS_ID=$( \
        aws sts get-caller-identity \
          --query 'Account' \
          --output text \
) \
        && echo ${AWS_ID}
結果(例)

      XXXXXXXXXXXX

2. APIの作成

2.1. APIの作成

APIを作成するときは、説明も必ず入れるようにしましょう。

変数の設定
APIGW_API_DESC='API for slack-echo-command'
変数の確認
cat << ETX

        APIGW_API_NAME: ${APIGW_API_NAME}
        APIGW_API_DESC: "${APIGW_API_DESC}"

ETX
コマンド
aws apigateway create-rest-api \
        --name ${APIGW_API_NAME} \
        --description "${APIGW_API_DESC}"
結果(例)

      {
        "id": "xxxxxxxxxx",
        "name": "slack-echo-command-20160829",
        "description": "API for slack-echo-command",
        "createdDate": 1448002904
      }

2.2. APIの確認

コマンド
aws apigateway get-rest-apis \
        --query "items[?name == \`${APIGW_API_NAME}\`].name"
結果(例)

      [
        "slack-echo-command-20160829"
      ]

2.3. API IDの取得

コマンド
APIGW_API_ID=$( \
        aws apigateway get-rest-apis \
          --query "items[?name == \`${APIGW_API_NAME}\`].id" \
          --output text \
) \
        && echo ${APIGW_API_ID}
結果(例)

      xxxxxxxxxx

2.4. APIの確認

コマンド
aws apigateway get-rest-api \
        --rest-api-id ${APIGW_API_ID}
結果(例)

      {
        "id": "xxxxxxxxxx",
        "name": "slack-echo-command-20160829",
        "description": "API for slack-echo-command",
        "createdDate": 1445947998
      }

2.5. モデルの確認

コマンド
aws apigateway get-models \
        --rest-api-id ${APIGW_API_ID}
結果(例)

      {
        "items": [
          {
              "description": "This is a default empty schema model",
              "schema": "{\n  \"$schema\": \"http://json-schema.org/draft-04/schema#\",\n  \"title\" : \"Empty Schema\",n  \"type\" : \"object\"\n}",
              "contentType": "application/json",
              "id": "xxxxxx",
              "name": "Empty"
          },
          {
              "description": "This is a default error schema model",
              "schema": "{\n  \"$schema\" : \"http://json-schema.org/draft-04/schema#\",\n  \"title\" : \"Error Schema\",\n  \"type\" : \"object\",\n  \"properties\" : {\n    \"message\" : { \"type\" : \"string\" }\n  }\n}",
              "contentType": "application/json",
              "id": "xxxxxx",
              "name": "Error"
          }
        ]
      }

3. リソースの作成

3.1. 現在のリソースの確認

コマンド
aws apigateway get-resources \
        --rest-api-id ${APIGW_API_ID}
結果(例)

      {
        "items": [
          {
              "path": "/",
              "id": "xxxxxxxxxx"
          }
        ]
      }

3.2. 現在のリソースのリソースID取得

変数の設定
APIGW_RESOURCE_PATH="/"
コマンド
APIGW_RESOURCE_ID=$( \
        aws apigateway get-resources \
          --rest-api-id ${APIGW_API_ID} \
          --query "items[?path == \`${APIGW_RESOURCE_PATH}\`].id" \
          --output text \
) \
        && echo ${APIGW_RESOURCE_ID}
結果(例)

      xxxxxxxxxx
コマンド
aws apigateway get-resource \
        --rest-api-id ${APIGW_API_ID} \
        --resource-id ${APIGW_RESOURCE_ID}
結果(例)

      {
        "path": "/",
        "id": "xxxxxxxxxx"
      }

3.3. 新しいリソースの作成

変数の設定
APIGW_PARENT_ID="${APIGW_RESOURCE_ID}"
APIGW_PATH_PART='sayhello'
変数の確認
cat << ETX

        APIGW_API_ID:    ${APIGW_API_ID}
        APIGW_PARENT_ID: ${APIGW_PARENT_ID}
        APIGW_PATH_PART: ${APIGW_PATH_PART}

ETX
コマンド
aws apigateway create-resource \
        --rest-api-id ${APIGW_API_ID} \
        --parent-id ${APIGW_PARENT_ID} \
        --path-part ${APIGW_PATH_PART}
結果(例)

      {
        "path": "/sayhello",
        "pathPart": "sayhello",
        "id": "xxxxxx",
        "parentId": "xxxxxxxxxx"
      }

3.4. 新しいリソースのリソースID取得

変数の設定
APIGW_RESOURCE_PATH="${APIGW_RESOURCE_PATH}${APIGW_PATH_PART}" \
        && echo ${APIGW_RESOURCE_PATH}
コマンド
APIGW_RESOURCE_ID=$( \
        aws apigateway get-resources \
          --rest-api-id ${APIGW_API_ID} \
          --query "items[?path == \`${APIGW_RESOURCE_PATH}\`].id" \
          --output text \
) \
        && echo ${APIGW_RESOURCE_ID}
結果(例)

      xxxxxxxxxx

3.5. 新しいリソースの内容確認

コマンド
aws apigateway get-resource \
        --rest-api-id ${APIGW_API_ID} \
        --resource-id ${APIGW_RESOURCE_ID}
結果(例)

      {
        "path": "/sayhello",
        "pathPart": "sayhello",
        "id": "xxxxxx",
        "parentId": "gca9ho3sa4"
      }

4. メソッドの作成

4.1. Lambda関数の指定

変数の設定
LAMBDA_FUNC_NAME='slack-echo-command-20160829'
コマンド
LAMBDA_FUNC_ARN=$( \
        aws lambda get-function \
          --function-name ${LAMBDA_FUNC_NAME} \
          --query 'Configuration.FunctionArn' \
          --output text \
) \
        && echo ${LAMBDA_FUNC_ARN}
結果(例)

      arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:slack-echo-command-20160829

4.2. API Gateway Integration URLの指定

変数の設定
APIGW_INTEG_URI="arn:aws:apigateway:${AWS_DEFAULT_REGION}:lambda:path/2015-03-31/functions/${LAMBDA_FUNC_ARN}/invocations" \
        && echo ${APIGW_INTEG_URI}
結果(例)

      arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:slack-echo-command-20160829

4.3. HTTPメソッドの作成

変数の設定
HTTP_METHOD='POST'
AUTH_TYPE='NONE'
変数の確認
cat << ETX

        APIGW_API_ID:      ${APIGW_API_ID}
        APIGW_RESOURCE_ID: ${APIGW_RESOURCE_ID}
        HTTP_METHOD:       ${HTTP_METHOD}
        AUTH_TYPE:         ${AUTH_TYPE}

ETX
コマンド
aws apigateway put-method \
        --rest-api-id ${APIGW_API_ID} \
        --resource-id ${APIGW_RESOURCE_ID} \
        --http-method ${HTTP_METHOD} \
        --authorization-type ${AUTH_TYPE}
結果(例)

      {
        "apiKeyRequired": false,
        "httpMethod": "POST",
        "authorizationType": "NONE"
      }
コマンド
aws apigateway get-method \
        --rest-api-id ${APIGW_API_ID} \
        --resource-id ${APIGW_RESOURCE_ID} \
        --http-method ${HTTP_METHOD}
結果(例)

      {
        "apiKeyRequired": false,
        "httpMethod": "POST",
        "authorizationType": "NONE"
      }
コマンド
aws apigateway get-resource \
        --rest-api-id ${APIGW_API_ID} \
        --resource-id ${APIGW_RESOURCE_ID}
結果(例)

      {
        "resourceMethods": {
          "POST": {}
        },
        "pathPart": "sayhello",
        "parentId": "xxxxxxxxxx",
        "path": "/sayhello",
        "id": "xxxxxx"
      }

4.4. integrationの作成

コマンド
aws apigateway get-integration \
        --rest-api-id ${APIGW_API_ID} \
        --resource-id ${APIGW_RESOURCE_ID} \
        --http-method ${HTTP_METHOD}
結果(例)

      A client error (NotFoundException) occurred when calling the GetIntegration operation: No integration defined for method

Integration Typeの指定

変数の設定
APIGW_INTEG_TYPE='AWS'

IntegrationのHTTPメソッド指定

変数の設定
APIGW_INTEG_METHOD='POST'

Integration RequestのBody Mapping Templates作成

変数の設定
FILE_INPUT="${APIGW_RESOURCE_ID}-BODY_MAPPING_TEMPLATE.json" \
        && echo ${FILE_INPUT}
コマンド
cat << \EOF > ${FILE_INPUT}
      {
        "application/x-www-form-urlencoded": "{ \"body\": $input.json(\"$\") }"
      }
EOF

JSONファイルを作成したら、フォーマットが壊れてないか必ず確認します。

コマンド
jsonlint -q ${FILE_INPUT}

エラーが出力されなければOKです。

変数の設定
APIGW_PASSTHROUGH_BEHAVIOR='WHEN_NO_TEMPLATES'
変数の確認
cat << ETX

        APIGW_API_ID:               ${APIGW_API_ID}
        APIGW_RESOURCE_ID:          ${APIGW_RESOURCE_ID}
        HTTP_METHOD:                ${HTTP_METHOD}
        APIGW_INTEG_TYPE:           ${APIGW_INTEG_TYPE}
        APIGW_INTEG_METHOD:         ${APIGW_INTEG_METHOD}
        APIGW_INTEG_URI:            ${APIGW_INTEG_URI}
        FILE_INPUT:                 ${FILE_INPUT}
        APIGW_PASSTHROUGH_BEHAVIOR: ${APIGW_PASSTHROUGH_BEHAVIOR}

ETX
コマンド
aws apigateway put-integration \
        --rest-api-id ${APIGW_API_ID} \
        --resource-id ${APIGW_RESOURCE_ID} \
        --http-method ${HTTP_METHOD} \
        --type ${APIGW_INTEG_TYPE} \
        --integration-http-method ${APIGW_INTEG_METHOD} \
        --uri ${APIGW_INTEG_URI} \
        --request-templates file://${FILE_INPUT} \
        --passthrough-behavior ${APIGW_PASSTHROUGH_BEHAVIOR}
結果(例)

      {
        "passthroughBehavior": "WHEN_NO_TEMPLATES",
        "cacheKeyParameters": [],
        "uri": "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:slack-echo-command-20160829/invocations",
        "httpMethod": "POST",
        "requestTemplates": {
          "application/x-www-form-urlencoded": "{ \"body\": $input.json(\"$\") }"
        },
        "cacheNamespace": "xxxxxx",
        "type": "AWS"
      }
コマンド
aws apigateway get-integration \
        --rest-api-id ${APIGW_API_ID} \
        --resource-id ${APIGW_RESOURCE_ID} \
        --http-method ${HTTP_METHOD}
結果(例)
      {
        "passthroughBehavior": "WHEN_NO_TEMPLATES",
        "cacheKeyParameters": [],
        "uri": "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:slack-echo-command-20160829/invocations",
        "httpMethod": "POST",
        "requestTemplates": {
          "application/x-www-form-urlencoded": "{ \"body\": $input.json(\"$\") }"
        },
        "cacheNamespace": "xxxxxx",
        "type": "AWS"
      }

4.5. Integration Responseの作成

POSTメソッドのリザルトコード200でのIntegration Responseが存在作成しな
いことを確認します。

変数の設定
HTTP_STATUS_CODE='200'
コマンド
aws apigateway get-integration-response \
        --rest-api-id ${APIGW_API_ID} \
        --resource-id ${APIGW_RESOURCE_ID} \
        --http-method ${HTTP_METHOD} \
        --status-code ${HTTP_STATUS_CODE}
結果(例)

      A client error (NotFoundException) occurred when calling the GetMethodResponse operation: Invalid Response status code specified

POSTメソッドのリザルトコード200でのIntegration Responseを作成します。

変数の確認
cat << ETX

        APIGW_API_ID:       ${APIGW_API_ID}
        APIGW_RESOURCE_ID:  ${APIGW_RESOURCE_ID}
        HTTP_METHOD:        ${HTTP_METHOD}
        HTTP_STATUS_CODE:   ${HTTP_STATUS_CODE}

ETX
コマンド
aws apigateway put-integration-response \
        --rest-api-id ${APIGW_API_ID} \
        --resource-id ${APIGW_RESOURCE_ID} \
        --http-method ${HTTP_METHOD} \
        --status-code ${HTTP_STATUS_CODE} \
        --selection-pattern ".*"
結果(例)

      {
        "selectionPattern": ".*",
        "statusCode": "200"
      }

POSTメソッドのリザルトコード200でのIntegration Responseを確認します。

コマンド
aws apigateway get-integration-response \
        --rest-api-id ${APIGW_API_ID} \
        --resource-id ${APIGW_RESOURCE_ID} \
        --http-method ${HTTP_METHOD} \
        --status-code ${HTTP_STATUS_CODE}
結果(例)
{
    "selectionPattern": ".*", 
    "statusCode": "200"
}
コマンド
aws apigateway get-integration \
        --rest-api-id ${APIGW_API_ID} \
        --resource-id ${APIGW_RESOURCE_ID} \
        --http-method ${HTTP_METHOD}
結果(例)

      {
        "integrationResponses": {
          "200": {
              "selectionPattern": ".*",
              "statusCode": "200"
          }
        },
        "passthroughBehavior": "WHEN_NO_TEMPLATES",
        "cacheKeyParameters": [],
        "uri": "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:slack-echo-command-20160829/invocations",
        "httpMethod": "POST",
        "requestTemplates": {
          "application/x-www-form-urlencoded": "{ "body": $input.json("$") }"
        },
        "cacheNamespace": "hrs5il",
        "type": "AWS"
      }

4.6. Lambda関数の実行権限付与

POSTメソッドのリザルトコード200でのIntegration Responseに、Lambda関数
を実行する権限を付与します。

Statement IDを乱数32桁で生成します。

変数の設定
LAMBDA_STAT_ID=$(od -vAn -N16 -tx < /dev/urandom | sed 's/ //g')
変数の設定
RESOURCE_PATH="${APIGW_RESOURCE_PATH}"
変数の確認
cat << ETX

        AWS_ID:        ${AWS_ID}
        APIGW_API_ID:  ${APIGW_API_ID}
        HTTP_METHOD:   ${HTTP_METHOD}
        RESOURCE_PATH: ${RESOURCE_PATH}

ETX
変数の設定
SOURCE_ARN="arn:aws:execute-api:${AWS_DEFAULT_REGION}:${AWS_ID}:${APIGW_API_ID}/*/${HTTP_METHOD}${RESOURCE_PATH}" \
        && echo ${SOURCE_ARN}
変数の設定
LAMBDA_PERMIT_ACTION='lambda:InvokeFunction'
      LAMBDA_PERMIT_PRINCIPAL='apigateway.amazonaws.com'
      RESOURCE_PATH="${APIGW_RESOURCE_PATH}"
変数の確認
cat << ETX

        LAMBDA_FUNC_NAME:        ${LAMBDA_FUNC_NAME}
        LAMBDA_STAT_ID:          ${LAMBDA_STAT_ID}
        LAMBDA_PERMIT_ACTION:    ${LAMBDA_PERMIT_ACTION}
        LAMBDA_PERMIT_PRINCIPAL: ${LAMBDA_PERMIT_PRINCIPAL}
        SOURCE_ARN:              ${SOURCE_ARN}

ETX
コマンド
aws lambda add-permission \
        --function-name ${LAMBDA_FUNC_NAME} \
        --statement-id ${LAMBDA_STAT_ID} \
        --action ${LAMBDA_PERMIT_ACTION} \
        --principal ${LAMBDA_PERMIT_PRINCIPAL} \
        --source-arn ${SOURCE_ARN}
結果(例)

      {
        "Statement": "{\"Condition\":{\"ArnLike\":{\"AWS:SourceArn\":\"arn:aws:execute-api:ap-northeast-1:XXXXXXXXXXXX:xxxxxxxxxx/*/POST/sayhello\"}},\"Action\":[\"lambda:InvokeFunction\"],\"Resource\":\"arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:slack-echo-command-20160829\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"apigateway.amazonaws.com\"},\"Sid\":\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"}"
      }
コマンド
aws lambda get-policy \
        --function-name ${LAMBDA_FUNC_NAME} \
        | sed 's/\\//g' | sed 's/\"{/{/' | sed 's/\"$//' \
        | jp.py "Policy.Statement[?Sid == \`${LAMBDA_STAT_ID}\`]"
結果(例)
[
    {
        "Resource": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:slack-echo-command-20160828",
        "Effect": "Allow",        "Sid": "54dee6c20e66aa1d1af183b2b8a2796a",
        "Action": "lambda:InvokeFunction",
        "Condition": {
            "ArnLike": {
                "AWS:SourceArn": "arn:aws:execute-api:ap-northeast-1:XXXXXXXXXXXX:npvefdajeb/*/POST/sayhello"
            }        },
        "Principal": {
            "Service": "apigateway.amazonaws.com"
        }
    }
]

4.7. Method Responseの作成

コマンド
aws apigateway get-method-response \
        --rest-api-id ${APIGW_API_ID} \
        --resource-id ${APIGW_RESOURCE_ID} \
        --http-method ${HTTP_METHOD} \
        --status-code ${HTTP_STATUS_CODE}
結果(例)

      A client error (NotFoundException) occurred when calling the GetIntegrationResponse operation: Invalid Response status code specified
コマンド
aws apigateway put-method-response \
        --rest-api-id ${APIGW_API_ID} \
        --resource-id ${APIGW_RESOURCE_ID} \
        --http-method ${HTTP_METHOD} \
        --status-code ${HTTP_STATUS_CODE} \
        --response-models '{}'
結果(例)

      {
        "responseModels": {},
        "statusCode": "200"
      }
コマンド
aws apigateway get-method-response \
        --rest-api-id ${APIGW_API_ID} \
        --resource-id ${APIGW_RESOURCE_ID} \
        --http-method ${HTTP_METHOD} \
        --status-code ${HTTP_STATUS_CODE}
結果(例)

      {
        "responseModels": {},
        "statusCode": "200"
      }
コマンド
aws apigateway get-method \
        --rest-api-id ${APIGW_API_ID} \
        --resource-id ${APIGW_RESOURCE_ID} \
        --http-method ${HTTP_METHOD}
結果(例)

      {
        "apiKeyRequired": false,
        "httpMethod": "POST",
        "methodIntegration": {
          "integrationResponses": {
              "200": {
                  "selectionPattern": ".*",
                  "statusCode": "200"
              }
          },
          "passthroughBehavior": "WHEN_NO_TEMPLATES",
          "cacheKeyParameters": [],
          "uri": "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:slack-echo-command-20160828/invocations",
          "httpMethod": "POST",
          "requestTemplates": {
              "application/x-www-form-urlencoded": "{ "body": $input.json("$") }"
          },
          "cacheNamespace": "0z3hfw",
          "type": "AWS"
        },
        "methodResponses": {
          "200": {
              "responseModels": {},
              "statusCode": "200"
          }
        },
        "authorizationType": "NONE"
      }

5. APIのデプロイ

5.1. APIのデプロイ

コマンド
aws apigateway get-deployments \
        --rest-api-id ${APIGW_API_ID}
結果(例)

      {
          "items": []
      }
変数の設定
APIGW_STAGE_NAME='prod'
APIGW_STAGE_DESC='This is a prod.'
APIGW_DEPLOY_DESC='Calling Lambda functions blueprint.'
変数の確認
cat << ETX

        APIGW_API_ID:      ${APIGW_API_ID}
        APIGW_STAGE_NAME:  ${APIGW_STAGE_NAME}
        APIGW_STAGE_DESC:  "${APIGW_STAGE_DESC}"
        APIGW_DEPLOY_DESC: "${APIGW_DEPLOY_DESC}"

ETX
コマンド
aws apigateway create-deployment \
        --rest-api-id ${APIGW_API_ID} \
        --stage-name ${APIGW_STAGE_NAME} \
        --stage-description "${APIGW_STAGE_DESC}" \
        --description "${APIGW_DEPLOY_DESC}"
結果(例)

      {
        "description": "Calling Lambda functions blueprint.",
        "id": "xxxxxx",
        "createdDate": 1448115971
      }

5.2. APIのステージ名の取得

コマンド
aws apigateway get-stages \
        --rest-api-id ${APIGW_API_ID}
結果(例)

      {
        "item": [
          {
              "description": "This is a prod.",
              "stageName": "test",
              "cacheClusterEnabled": false,
              "cacheClusterStatus": "NOT_AVAILABLE",
              "deploymentId": "xxxxxx",
              "lastUpdatedDate": 1448115971,
              "createdDate": 1448115971,
              "methodSettings": {}
          }
        ]
      }
コマンド
APIGW_STAGE_NAME=$( \
        aws apigateway get-stages \
          --rest-api-id ${APIGW_API_ID} \
          --query "item[?description == \`${APIGW_STAGE_DESC}\`].stageName" \
          --output text \
) \
        && echo ${APIGW_STAGE_NAME}
結果(例)

      prod
コマンド
aws apigateway get-stage \
        --rest-api-id ${APIGW_API_ID} \
        --stage-name ${APIGW_STAGE_NAME}
結果(例)

      {
        "stageName": "prod",
        "description": "This is a prod.",
        "cacheClusterEnabled": false,
        "cacheClusterStatus": "NOT_AVAILABLE",
        "deploymentId": "xxxxxx",
        "lastUpdatedDate": 1448115971,
        "createdDate": 1448115971,
        "methodSettings": {}
      }

6. APIのテスト

変数の設定
APIGW_URI="https://${APIGW_API_ID}.execute-api.${AWS_DEFAULT_REGION}.amazonaws.com/${APIGW_STAGE_NAME}${APIGW_RESOURCE_PATH}" \
        && echo ${APIGW_URI}

7. Slack (API Endpoint追加)

  1. https://${SLACK_TEAM_NAME}.slack.com/apps にアクセスします。(App Directory 画 面)

    • 検索窓に 'Slash Commands' と入力します。
    • 'Slash Commands'が表示されるので、クリックします。
  2. Browse Apps > Slash Commands (画面)

    • (ボタン)をクリックします。
  3. Edit configuration (画面)

    • Integration Settings
      • URL: API Gatewayのエンドポイント

8. slackコマンドの実行

slackから、/sayhelloコマンドに続いて英数字半角のテキストを入力してください。

/sayhello ec2

完了

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3