GitHub ActionsでCloudFront + S3にファイルをアップロードする

先日、仕事で初めてAWSでシステムを構築したんですが、S3のアップロード方法を全く考えられておらず、S3に直アップロードという方法を取りました。
当然、チームでの作業にかなり支障をきたしてしまいました・・・。
今回はその反省としてGitHub Actionsを使ってS3にアップロードすることに成功したので、備忘録として書いておきます。

CloudFront経由のS3の公開については過去記事にて紹介しているので割愛します。 suguru-no-rururu.hatenablog.com

1.レポジトリ作成

まずはgithubに管理用のレポジトリを作成します。 久々にレポジトリ作ると毎回忘れているのでこの記事を参考にしています。 qiita.com

2. Github Actionsの設定

レポジトリが作成できたら、Actionsをクリック。
「Set up this workflow」があるので、それをクリック。 f:id:katttsu105:20210508121910p:plain

Actionsには以下のように書きました。
access_key_idとsecret_access_keyは後々githubで設定するので、直書きはしないでください。

name: CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2
      
      - name: Configure AWS credentials from account
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.TEST_AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.TEST_AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-1

      - name: Copy Files to s3
        run: |
          aws s3 sync ./public s3://test-aws-service-k --delete

コードの紹介をしていきます。 mainというブランチにpushされた時にjobsが走るようにしています。
masterなど使っているブランチ名に変更してください。

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

リージョンはS3のバケットがあるリージョンに変更してください。

aws-region: ap-northeast-1

今回はpublic配下にあるファイルを付け足すようにしています。
--deleteオプションはS3のバケットにあるが、githubのレポジトリにないファイル・ディレクトリを削除するものです。

aws s3 sync ./public s3://test-aws-service-k --delete

3. SercretsにAWSのaccess_key_idとsecret_access_keyを設定

続いてgithubのSettings > サイドバーのSecretsから「New repository secret」をクリック。 f:id:katttsu105:20210508132811p:plain

そちらにIAMユーザーのaccess_key_idとsecret_access_keyを貼り付けます。
keyは先ほど、Github Actionsに設定したTEST_AWS_ACCESS_KEY_IDTEST_AWS_SECRET_ACCESS_KEYとします。 f:id:katttsu105:20210508133215p:plain

以上で設定は完了です。
作成したレポジトリにpushするとS3のバケットに反映されていると思います。

AWS SESの登録~メールアドレス認証・サンドボックス解除~

今回はAWS SESでメールを送信する機能を作成したので、そのメモとして残しておきます。
まずはSESを普通に使えるようにするために設定していきます。

メールアドレスの認証

まずはAWSコンソールでSES(Simple Email Service)を開く。
サイドバーの「Verified identities」をクリック。
すると、メールアドレスの認証画面が出るので、Create identityを選択。 f:id:katttsu105:20210504114725p:plain

Identity TypeをEmail addressにし、Email addressのところに自分が使用したいメールアドレス(送信元のメールアドレス)を入力します。

f:id:katttsu105:20210504115313p:plain

すると、メールのリンクをクリックして確認してくださいというお知らせがくるので、AWSから届いているメールのリンクをクリックします。 f:id:katttsu105:20210504115953p:plain

こんな感じのメールが届いていると思います。 f:id:katttsu105:20210504120340p:plain

リンクをクリックして、先ほどのSESのメールアドレス認証の画面に戻ると、Identity statusがVerifedになっていると思います。
以上でメールアドレスの認証は終了です。 f:id:katttsu105:20210504120505p:plain

サンドボックスから除外する

SESに登録すると、まずはサンドボックスというところに置かれます。
ここに置かれたままだと、

  • 認証したメールアドレス宛にしかメールを送信できない
  • 1日200通まで送信可能

など、使い物になりません。
そのため、このサンドボックスから外すという作業が必要になります。 ちなみに迷惑メール送信防止の観点などからサンドボックスがあるようです。

以下のリンクを開きます。 https://console.aws.amazon.com/ses/

そして、サイドバーのSending Statisticsをクリック。
Production AccessがSandBoxになっています。
設定をするために、真ん中あたりのEdit your account detailsをクリック。 f:id:katttsu105:20210504122729p:plain

ポップアップが出てくるので、一つづつ入力していきます。

  • Enable Production AccessをYes。
  • Mail Typeは使用用途によってTransactionalにするか、Marketingにするか分けてください。
    自分はMarketingにしました。
  • Website URLはご自身が使いたいWebサイトのURL。
  • Use case descriptionは以下について書くと良いそうです。日本語で大丈夫です。
    • ユースケースの説明
    • メーリングリストの管理をどうやってやるか
    • バウンスや苦情があった時の対処法
    • オプトアウトをどうやってやるか

自分が申請した時は全て合計して300文字くらいで書きました。
適当に提出すると落ちるので、数百文字くらいは書いておきましょう。
入力ができたらSubmit for reviewを押します。

f:id:katttsu105:20210504123123p:plain

申請を送信すると、送信内容がメールで来ます。
解除には24時間以内に対応すると書いてあるので、少し待ちます。
対応までの時間は結構バラバラでした。
初めは10時間くらい経ってから解除されたんですが、2回目は15分ほどで解除されたりなどでした。

申請が解除されるとstatusがEnabledになります。
これにてサンドボックスの解除が完了です。 f:id:katttsu105:20210504124451p:plain

参考:

Lambda+Cognitoでユーザーの登録

LambdaからCognitoのユーザープールにユーザーを作成することが仕事であったので、その方法をメモしておきます。

ユーザープール作成

AWSコンソールからCognitoを開きます。
ユーザープールの管理を選択します。 f:id:katttsu105:20210502161951p:plain

右上の「ユーザープールを作成する」を選択。 f:id:katttsu105:20210502162240p:plain

プール名を入力し、ステップに従って設定するを選択。 f:id:katttsu105:20210502162627p:plain

今回はemailも入力させたいので、emailにチェックを入れました。 f:id:katttsu105:20210502163811p:plain

続いてサイドバーのポリシーからパスワードの強度を設定します。
今回は一旦小文字だけにしました。
実際に使う場合にはよりセキュアにした方が良いと思います。 f:id:katttsu105:20210502164157p:plain

サイドバーのアプリクライアントから「アプリクライアントの追加」を押します。 f:id:katttsu105:20210502164441p:plain

すると、アプリクライアントの作成画面に移行するので、画面通り設定していきます。
アプリクライアント名は任意の名前で大丈夫です。 f:id:katttsu105:20210502165110p:plain

設定ができたらアプリクライアントの作成を押します。 f:id:katttsu105:20210502165242p:plain

その他の設定はデフォルトで大丈夫です。
最後にサイドバーの確認から入力内容を確認し、プールの作成を押します。 f:id:katttsu105:20210502165519p:plain

プールの作成ができたら、プールID・プールARNをメモしておきます。 f:id:katttsu105:20210502165713p:plain

サイドバーのアプリクライアントから、アプリクライアントIDもメモしておきます。 f:id:katttsu105:20210502170013p:plain

フェデレーティッドアイデンティティの作成

続いて、フェデレーティッドアイデンティティを作成します。
ユーザープールの右のフェデレーティッドアイデンティティを選択。 f:id:katttsu105:20210502170302p:plain

新しいIDプールの作成を押します。 f:id:katttsu105:20210502170411p:plain すると作成画面に移行するので、IDプール名を入力します。
こちらも任意の名前で大丈夫です。 f:id:katttsu105:20210502170639p:plain

下にスクロールすると、認証プロバイダーというところがあるので、
こちらに先ほどメモしたユーザープールIDとアプリクライアントIDを入力します。
入力ができたらプールの作成を押します。

f:id:katttsu105:20210502170909p:plain

英語で説明が出てきますが、許可で大丈夫です。 f:id:katttsu105:20210502171243p:plain

作成完了画面が出てくるので、IDプールのIDをメモしておきます。 f:id:katttsu105:20210502171457p:plain

以上でユーザープールの作成は完了です。

Lambdaにコードを記述

今回はadminCreateUserを使用し、ユーザー登録を行いました。

'use strict';
const AWS = require('aws-sdk');
const cognito = new AWS.CognitoIdentityServiceProvider();
const querystring = require('querystring')

exports.handler = async (event, context, callback) => {
  const parse = querystring.parse
  const request_body = parse(event.body);
  const userId = request_body.email
  
  const params = {
    Username: userId,
    MessageAction: 'SUPPRESS',
    UserPoolId: '先ほどメモしたユーザーIDを入力',
    UserAttributes: [
      {
        Name: 'email',
        Value: userId,
      },
      {
        Name: 'email_verified',
        Value: 'true',
      },
      {
        Name: 'phone_number_verified',
        Value: 'false',
      },
      ],
  };
  
  let result = '';
  try {
      result = await cognito.adminCreateUser(params).promise()
    } catch (error) {
      return console.log('エラーです');
    }
      return console.log('登録完了!!')
  
    // TODO implement
    const response = {
        statusCode: 200,
        body: JSON.stringify('Hello from Lambda!'),
    };
    callback(null, event);
};

LambdaからHubSpotのコンタクトを作成・更新する

今回はAWS LambdaからHubSpotのAPIを叩く機会があったので、それについてまとめておきます。
調べてみると意外とやってみたという記事がほとんどなく、もしかしたら稀なケースなのかもしれません。
LambdaのランタイムはNode.jsを使用しています。

Lambda自体の動かし方については前回の記事でまとめているので、そちらをご覧ください。 suguru-no-rururu.hatenablog.com

HubSpotは開発者用のアカウントを作成すると、試しにコンタクトを作成したり削除できたりするので、作っておくことをお勧めします。 developers.hubspot.jp

モジュールをインストールし、zip化する

今回使用しているrequestですが、Lambdaでパッケージをインストールしなければならなかったので、その方法も記述しておきます。

手順

  1. ローカル環境(Desktopとか)にnodejs/node_modulesフォルダを作成
  2. nodejs/node_modulesで$ npm install request @hubspot/api-client
  3. $ zip -r request.zip nodejs/nodejsファイルごとzip化する(macであればfinderからzipすることも可能)

先ほど作成したzipをLambdaのレイヤーに追加する

Lambdaのサイドバー > レイヤーからレイヤーの作成を押します。 f:id:katttsu105:20210501205905p:plain

作成画面で先ほどzip化したファイルをアップロードします。 f:id:katttsu105:20210501210232p:plain 名前は任意のものを入力し、ランタイムはNode.jsとしておきます。

作成ができたら、Lambda関数のところに戻り、Layersからレイヤーの追加をします。 f:id:katttsu105:20210501210350p:plain

以上でレイヤーの追加は完了です。

コンタクトを取得する場合

var request = require("request");
var hs_get_options = { method: 'GET',
    url: `https://api.hubapi.com/contacts/v1/contact/email/${email}/profile`,
    qs: { hapikey: 'ここにHubSpotのAPIキーが入る' }}

   request(hs_get_options, function (error, response, body) {
   // このbodyにプロパティ情報が入っている
   // data.properties.hs_object_idなどで取り出し可能
    const data = JSON.parse(body)
})

コンタクトがなければ作成、あれば更新する場合

var request = require("request");
 var options = {
    method: 'POST',
    url: `https://api.hubapi.com/contacts/v1/contact/createOrUpdate/email/{email}/`,
    qs: {hapikey: 'ここにHubSpotのAPIキーが入る'},
    headers: { 'Content-Type': 'application/json'},
    body: 
    { properties: 
       [ { property: 'firstname', value: '' },
       { property: 'lastname', value: name },
        { property: 'その他必要なproperty', value: '' } ] },
    json: true };
  // -------------------------------
  const hs_response = {
        statusCode: 200,
        body: JSON.stringify(await new Promise((resolve, reject) => {
            request(options, (err, resp, b1) => {
                if (err) {
                } else {
                    resolve(resp)
                }
            })    
        })),
    };

参考:

legacydocs.hubspot.com

コンタクトを作成する場合

上のコードのurlをhttps://api.hubapi.com/contacts/v1/contact/に変更
propertiesに{ property: 'email', value: '登録したいemailを入れる' }を挿入すればOKです。

コンタクトを更新する場合

var request = require("request");
  var hs_update_options = { method: 'POST',
    url: `https://api.hubapi.com/contacts/v1/contact/vid/{HubSpotのコンタクトのID}/profile`,
        qs: { hapikey: 'ここにHubSpotのAPIキーが入る' },
        headers: 
          { 'Content-Type': 'application/json' },
        body: 
        { properties: 
          [ { property: '更新したい内部値', value: 'バリュー' } ] },
          json: true };
        request(hs_update_options, function (error, response, body) {
        });
  }

参考:HubSpot APIドキュメント

legacydocs.hubspot.com

developers.hubspot.com

S3 + API Gatewayを使ってLambdaを動かす

今回はAPI Gatewayを使ってLambdaを発火させることができたので、その方法をメモしておきます。
こちらは前回の続きです。

suguru-no-rururu.hatenablog.com

Lambda関数の作成

AWSコンソールでLambdaを開きます。 右上の「関数の作成」を選択。 f:id:katttsu105:20210430220729p:plain

すると、関数の作成画面が出てくるので、

任意の関数名を入力します。今回はtestFunctionとしました。
ランタイムはNode.js 14.xにしました。
ここは自由に選んで良いと思いますが、ネットの情報を見る感じ、PythonとNode.jsが多い印象なので、どちらかを選んでおくと無難です。

入力ができたら関数の作成ボタンを押す。 f:id:katttsu105:20210501001918p:plain

作成が完了するとこんな感じの画面になります。 f:id:katttsu105:20210501002910p:plain

コードを変更しておきましょう。
コードを選択し、サイドバーのindex.jsをopenにします。

f:id:katttsu105:20210501120400p:plain そして以下のようにコードを変更します。

exports.handler = (event, context, callback) => {
    // TODO implement
    const response = {
        statusCode: 200,
        body: JSON.stringify('Hello from Lambda!'),
    };
    callback(null, event);
};

変更ができたら必ずDeployを押してください。 f:id:katttsu105:20210501120635p:plain

続いてAPI Gatewayの設定に移ります。

API GatewayAPIの開設

AWSコンソールからAPI Gatewayの画面を開きます。
そして右上のAPIの作成を選択。 f:id:katttsu105:20210501003304p:plain

今回はREST APIを選択しました。
「構築」を選択。 f:id:katttsu105:20210501003707p:plain

画像のように設定していきます。
ここでもAPI名は任意のもので大丈夫です。
入力が完了したら、「APIの作成」を選択。
f:id:katttsu105:20210501004225p:plain

すると、APIのページに遷移します。
左上のアクションから「リソースの作成」を選択。 f:id:katttsu105:20210501004428p:plain

リソース名を入力します。
ここも任意のもので大丈夫です。
入力ができたら「リソースの作成」を選択します。 f:id:katttsu105:20210501004802p:plain

続いて、アクションからメソッドの作成をします。 f:id:katttsu105:20210501005043p:plain

先ほど作成したリソースの下に小さくメソッドを選択するとことが出てきます。
今回はformから送信ボタンを押した際に動作するAPIを作成したいので、POSTにしました。
選択ができたら、右のチェックボタンをクリックします。

f:id:katttsu105:20210501005314p:plain

セットアップ画面が出るので、ここで先ほど作成したLambda関数を選択します。

f:id:katttsu105:20210501005603p:plain

保存をクリックすると、権限を追加する警告が出ますが、OKで大丈夫です。 f:id:katttsu105:20210501005737p:plain

f:id:katttsu105:20210501005843p:plain

忘れずにAPIのデプロイをしておきましょう。
忘れがちですが、これをしないと設定が反映されません。 f:id:katttsu105:20210501112705p:plain

今回はステージ名をtest_lambdaとしました。 f:id:katttsu105:20210501112851p:plain デプロイをすると、先ほどのAPIのパスが表示されるのでメモしておきます。 f:id:katttsu105:20210501113352p:plain

マッピングテンプレートの追加もしておきます。
リソース > POST > 統合リクエストの順でクリックしていきます。 f:id:katttsu105:20210501114918p:plain

一番下にマッピングテンプレートというところがあるので、Content-Typeのところにapplication/x-www-form-urlencodedと入力します。
入力ができたら右のチェックマークを押します。 f:id:katttsu105:20210501115149p:plain すると下に入力するフォームが出てくるので、以下を入力します。

{
  "body" : $input.json('$')
}

入力ができたら保存してください。 f:id:katttsu105:20210501115350p:plain

以上でAPI Gatewayの設定は完了です。

S3にフォームのHTMLをアップロード

続いて先ほどのAPIを叩くためのフォームを用意します。
formタグのactionのところに先ほどのパスを貼り付けてください。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
</head>

<h1>S3#index.html<h1>
  <form action="https://{先ほどメモしたパスを貼り付け}/test_lambda/form" method="post">
    <input type="submit" value="送信">
  </form>
</html>

S3のアップロードから先ほど作成したindex.htmlをアップします。 f:id:katttsu105:20210501113859p:plain

今回はCloudFront経由で配信しているので、Domain Nameのところからページを開きます。

f:id:katttsu105:20210501114415p:plain 先ほどのフォームが見れると思います。 f:id:katttsu105:20210501120229p:plain

送信を押してみると、レスポンスの設定を何もしていないので、{}のような文字が出ると思います。

Lambdaのログを見てLambda関数が動いているか確認しましょう。
Lambdaの先ほど作成した関数から中央あたりのメニューから
モニタリング > CloudWatchのログを表示を選択。 f:id:katttsu105:20210501120944p:plain

こちらに何か記述があればLambdaが動いている証です。
リンクになっているところをクリックすると、中身が見れます。
Lambda内でconsole.logなどをすると、こちらに表示されます。 f:id:katttsu105:20210501121145p:plain

以上で終了です。

CloudFront経由でS3を公開する

f:id:katttsu105:20210429142631p:plain

今回はFrontCloud経由でS3にある静的ファイルを公開することができたので、その方法を書いておきます。

S3でバケットの作成

1.バケット作成ボタンを押す

AWSコンソールでS3のページにアクセスし、右上のバケットを作成を選択。 f:id:katttsu105:20210429144435p:plain

2.バケット名とリージョンを選択

バケット名を入力し、リージョンを選択します。
バケット名は任意の名前で構いません。今回はtest-aws-service-kとしました。
※バケット名はアカウント関係なく、すでに存在名前だと作成できないので注意してください。
リージョンも任意の場所で大丈夫です。 日本で使用する場合には東京リージョンにするのが一般的かと思います。 f:id:katttsu105:20210429150536p:plain

3. ブロックパブリックアクセス設定

今回はCloudFront経由の公開を考えているので、こちらは「パブリックアクセスをすべてブロック」で大丈夫です。
S3のまま公開したい場合にはこちらのチェックは外す必要があります。 f:id:katttsu105:20210429145604p:plain

4.その他の設定

基本的に理由がなければ以下の設定にしておくと良いと思います。 設定が完了したらバケットの作成を選択。

  • バケットのバージョニング: 有効
  • デフォルトの暗号化: 有効
  • 暗号化キータイプ: Amazon S3キー
  • 詳細設定: 変更なし

f:id:katttsu105:20210429150015p:plain

f:id:katttsu105:20210429150027p:plain

すると、バケット作成完了の通知がきます。 f:id:katttsu105:20210429150656p:plain

5.静的ファイルをホスティングする

先ほど作成したバケットが静的ファイルをホスティングするように設定していきます。
先ほど作成したバケットを選択する。 f:id:katttsu105:20210429151556p:plain プロパティ > (一番下にスクロール) 静的ウェブサイトホスティングを選択します。 f:id:katttsu105:20210429151611p:plain

f:id:katttsu105:20210429151633p:plain 画像通り設定していきます。 インデックスドキュメントはホームURLでどのファイルを表示させるかの設定になるので、今回はindex.htmlとしておきます。
もしサイトを開いたときにtop.htmlというファイルを表示させたければtop.htmlに変更するという感じです。
後でも変更できるので仮で決めてもOKです。 f:id:katttsu105:20210429151646p:plain

6. ファイルをアップロードする

ここで、先ほど作成したバケット今回表示させるファイルをアップロードします。 今回は仮にindex.htmlだけアップロードします。
以下のようなファイルを作りました。

index.html

<h1>S3#index.html<h1>

バケットからアップロードを選択します。 f:id:katttsu105:20210429152643p:plain

「ファイルを追加」を選択し、アップロードしたいファイルを追加します。 追加ができたら下の「アップロード」ボタンを押します。 f:id:katttsu105:20210429152823p:plain アップロードに成功すると通知が出ます。

f:id:katttsu105:20210429153106p:plain 以上でS3の設定は完了です。

CloudFrontの設定

続いてCloudFront側の設定をします。
AWSコンソールでCloudFrontを開きます。
左上に「Create Distribution」のボタンがあるので、そちらを選択。 f:id:katttsu105:20210429153435p:plain

画像のように設定していきます。
Origin Domain Nameには先ほど作成したバケットを選択します。
バケット名を少し打つと、サジェスチョンが出てくると思います。 f:id:katttsu105:20210429154111p:plain f:id:katttsu105:20210429154130p:plain f:id:katttsu105:20210429154145p:plain ここまで設定ができたら一番下のCreate Distributionを選択します。 f:id:katttsu105:20210429154201p:plain

すると、こちらのstatusが、In ProgressからDeployedになるまで待ちます。 f:id:katttsu105:20210429154657p:plain

Deployedになったら作成したDistributionを選択し、GeneralからDomain Nameをコピーします。 f:id:katttsu105:20210429160225p:plain

自分はindex.htmlというファイルをアップロードしたので、 ドメインname/index.htmlで先ほどのindex.htmlが表示されます! f:id:katttsu105:20210429160319p:plain

以上で設定完了です。
お疲れ様でした。

AWS CLIでアカウント切り替え

たまにAWS CLI使うと、アカウント切り替えとS3のダウンロードのコマンドを忘れてしまうので備忘録的に書いておきます。

アカウント変更

1. 現在のアカウントを確認する

$ cd ~/.aws
$ ls
config credentials
$  cat credentials

[default]
aws_access_key_id = wwwwwwwwwwwwwwwwww
aws_secret_access_key = xxxxxxxxxxxxxxxxxxxxxxxxx

[account_name]
aws_access_key_id = yyyyyyyyyyyyyyyyyyyyy
aws_secret_access_key = zzzzzzzzzzzzzzzzzzzzzzz

2. アカウント変更

先ほど確認したユーザー名を入れてアカウントを変更します。

$ export AWS_DEFAULT_PROFILE=ユーザー名

もし先ほどのaccount_nameに変更したい場合はこちら

$ export AWS_DEFAULT_PROFILE=account_name

アカウント確認すると、先ほど変更したアカウントになっている。

$ aws configure list  

      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile                  test           manual    --profile
access_key     ****************IEPM shared-credentials-file
secret_key     ****************VPOT shared-credentials-file
    region           ap-northeast-1      config-file    ~/.aws/config