1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

CodeCommitにサーバーレス(Lambda)の定期実行でPushする

Last updated at Posted at 2023-08-07

はじめに

CodeCommitで管理しているリポジトリにサーバーレスの定期実行でPushする、ということをやってみたのでそのやり方を残したいと思います。
本記事では、試しにYAHOO!ニュース(ITカテゴリ)のRSSを取得して、JSON形式で保存する。というユースケースでやり方の紹介をします。

目次

  1. はじめに
  2. 前提
  3. 準備
  4. 権限の設定
  5. Lambda関数作成
  6. Lambda関数の登録
  7. おわりに

前提

1. 以下のツールがインストールされていること。

node18
yarn
aws-cli

2. Push先のリポジトリの作成と、initial commitを済ませておく。

準備

今回Lambda使用するのはNode.js 18.xです。
yarnでプロジェクトを管理します。

yarn init

パッケージのインストールをします。

yarn add @aws-sdk/client-codecommit axios typescript xml2js
yarn add -D @types/aws-lambda @types/node @types/xml2js esbuild

今回はTypescriptで型定義をしつつコードを書いていきます。
RSSはxmlで取得されるので、xmlをjsonに変換するためにxml2jsを使用します。
esbuildはTypeScriptのコードをJavaScriptにトランスパイルするために使用します。

権限の設定

ロール

iamフォルダを作成し、その中にrole.jsonを作成します。

iam/role.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

ロールを作成します。

aws iam create-role --role-name lambda-codecommit --assume-role-policy-document file://iam/role.json

ポリシー

iamフォルダ下にpolicy.jsonを作成します。

iam/policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "codecommit:ListRepositories",
        "codecommit:ListBranches",
        "codecommit:GetBranch",
        "codecommit:GetCommit",
        "codecommit:PutFile"
      ],
      "Resource": "<リポジトリのARN>"
    }
  ]
}

ポリシーを作成します。

aws iam create-policy --policy-name put-file-to-codecommit --policy-document file://iam/policy.json

ロールにアタッチします。

aws iam attach-role-policy --role-name lambda-codecommit --policy-arn <先ほど作成したポリシーのARN>

Amazon CloudWatch Logsへの書き込み等を許可するポリシーをアタッチします。

aws iam attach-role-policy --role-name lambda-codecommit --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

Lambda関数作成

各種設定

Typescriptを使用しているので、tsconfig.jsonを作成します。

tsconfig.json
{
  "compilerOptions": {
    "target": "es2020",
    "module": "es2020",
    "moduleResolution": "node",
    "strict": true,
    "incremental": true,
    "outDir": "./.ts-cache/"
  }
}

簡潔に上記の設定だけ記述しました。

次に、esbuildの実行ファイルを作成します。

esbuild.config.mjs
import esbuild from 'esbuild';

esbuild.build({
    entryPoints: [
        './index.ts',
    ],
    outdir: 'dist',
    bundle: true,
    minify: true,
    platform: 'node',
    sourcemap: false,
    target: ['es2020'],
}).catch(() => process.exit(1));

CLIからパラメータを指定すれば実行できますが、今回はファイルで管理してみました。

次にpackage.jsonのscriptsセクションを定義します。

package.json
  "scripts": {
    "prebuild": "rm -rf dist",
    "build": "node esbuild.config.mjs",
    "postbuild": "cd dist && zip -r index.zip index.js*"
  }

esbuildコマンドの定義をしています。

Lambda関数

今回は役割ごとに複数ファイルに分けてみました。
index.tsがエントリーポイントになります。

index.ts
import { Handler, APIGatewayProxyResult } from 'aws-lambda';
import { putFile } from "./lib/codeCommitUtil";
import { CodeCommitClient } from '@aws-sdk/client-codecommit';

export const handler: Handler = async (): Promise<APIGatewayProxyResult> => {
    try {
        const REGION = process.env['REGION'];
        if (!REGION) throw new Error("REGION is null.");

        const client = new CodeCommitClient({ region: REGION });

        const result: string = await putFile(client);
        return {
            statusCode: 200,
            body: result
        };
    } catch (e) {
        console.log(e)
        if (e instanceof Error) {
            return {
                statusCode: 500,
                body: e.message
            };
        } else {
            return {
                statusCode: 500,
                body: 'An unknown error occurred'
            };
        }
    }
};

次にCodeCommitに関するコードを記述したファイルです。(libフォルダ下に作っています。)

lib/codeCommitUtil.ts
import { getData } from "./fetchDataUtil";
import {
    CodeCommitClient,
    GetBranchCommand,
    GetBranchCommandOutput,
    PutFileCommand,
    PutFileCommandOutput,
    SameFileContentException,
    GetBranchCommandInput,
    PutFileCommandInput
} from "@aws-sdk/client-codecommit";

export const putFile = async (client: CodeCommitClient): Promise<string> => {
    try {
        const REPOSITORY = process.env['REPOSITORY'];
        if (!REPOSITORY) throw new Error("REPOSITORY is null.");
        const BRANCH = process.env['BRANCH'];
        if (!BRANCH) throw new Error("BRANCH is null.");

        const data: Uint8Array = await getData();

        const latestCommitId: string = await getLatestCommitId(REPOSITORY, BRANCH, client);

        const params: PutFileCommandInput = {
            repositoryName: REPOSITORY,
            branchName: BRANCH,
            fileContent: data,
            filePath: 'test.json',
            commitMessage: 'Update file from Lambda function',
            parentCommitId: latestCommitId
        };
        const command: PutFileCommand = new PutFileCommand(params);

        const response: PutFileCommandOutput = await client.send(command);

        return 'Successfully put file. commitId = "' + response.commitId + '"';

    } catch (e) {
        if (e instanceof SameFileContentException) {
            return 'No changes in the data file';
        }
        throw e;
    }
};

const getLatestCommitId = async (repositoryName: string, branchName: string, client: CodeCommitClient): Promise<string> => {
    const params: GetBranchCommandInput = {
        repositoryName: repositoryName,
        branchName: branchName,
    };

    const command: GetBranchCommand = new GetBranchCommand(params);
    const response: GetBranchCommandOutput = await client.send(command);

    if (response.branch && typeof response.branch.commitId === 'string') {
        return response.branch.commitId;
    } else {
        throw new Error(`Failed to retrieve commit id.`);
    }
};

基本的にドキュメントを参考にパラメータ等を作っていきます。
今回は最小限必須パラメータのみで構成していますが、他にもパラメータは存在するので参照してみてください。

注意点:

  • PutFileCommandInputのparentCommitIdは、必須パラメータではありませんが、リポジトリが空ではない場合は必須になります。
  • fileContentの型はundefined | Uint8Arrayなので、パラメータはUint8Arrayに変換しておく必要があります。(下記ファイル参照)

次にデータを取得するファイルです。

lib/fetchDataUtil.ts
import axios, { AxiosResponse, AxiosError } from "axios";
import { parseStringPromise } from "xml2js";

export const getData = async (): Promise<Uint8Array> => {
    const response = await fetchRSSData();
    const xmlData = response.data;
    const jsonData = await xmlToJson(xmlData);
    const extractedJsonData = JSON.stringify(jsonData, null, 2);
    return new TextEncoder().encode(extractedJsonData);
};

async function fetchRSSData(): Promise<AxiosResponse<string>> {
    const DATA_URL = process.env['DATA_URL'];
    if (!DATA_URL) throw new Error("DATA_URL is null.");
    try {
        const response = await axios.get<string>(DATA_URL);
        return response;
    } catch (error) {
        console.log(error);
        throw Error(handleApiError(error as AxiosError));
    }
}

function handleApiError(error: AxiosError): string {
    if (error.response) {
        return `API Error: ${error.response.status} - ${error.response.statusText}`;
    } else if (error.request) {
        return 'Request failed. No response received.';
    } else {
        return 'An error occurred while processing the request.';
    }
}

async function xmlToJson(xmlData: string) {
    try {
        const jsonData = await parseStringPromise(xmlData, { trim: true });
        return jsonData;
    } catch (error) {
        console.log(error);
        throw Error('An error occurred while converting xml to json.');
    }
}

axiosでRSSを取得し、xml2jsでxmlをjsonに変換し、Uint8Arrayに変換したものを返しています。

Lambda関数の登録

コードが完成したので、Lambda関数をデプロイしていきます。

まず、yarn buildでTypescriptで書いたコードをJavascriptにトランスパイルし、zipに圧縮します。
distフォルダ下にトランスパイルされたjsファイルとzipファイルが生成されます。

下記CLIコマンドでLambda関数の登録を行います。

aws lambda create-function --function-name putFileToCodecommit --role <作成したロールのARN> --region ap-northeast-1 --zip-file fileb://dist/index.zip --runtime nodejs18.x --handler index.handler --timeout 10

次に環境変数を登録します。
極力CLIで済ませたいので、環境変数もファイルで定義しアップロードするようにします。

config/env.json
{
  "Variables":{
    "REGION": "ap-northeast-1",
    "REPOSITORY": "fetch-by-lambda",
    "BRANCH": "main",
    "DATA_URL": "https://news.yahoo.co.jp/rss/categories/it.xml"
  }
}

下記コマンドで環境変数を登録します。

aws lambda create-function-configuration --function-name putFileToCodecommit --environment file://config/env.json

下記コマンドでLambda関数のテストができます。

aws lambda invoke --function-name putFileToCodecommit out

EventBridge スケジュールを設定すれば、簡単にLambda関数を定期実行することができます。

おわりに

以上でCodeCommitで管理しているリポジトリにサーバーレスの定期実行でPushすることができました。
静的コンテンツをAmplify等でデプロイしていて、定期的にリポジトリを更新したい際に、Lambdaを使用すれば全てサーバーレスで完結できます。
Lambdaを使用した各種AWSサービスの操作はとても便利なので、ぜひ使いこなせるようになりたいです。

1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?