Help us understand the problem. What is going on with this article?

AWS::Include に相対パスを指定するときは package コマンドを実行するディレクトリからの相対パスにする!

More than 1 year has passed since last update.

※ この記事では AWS CLI v1.16.120, AWS SAM CLI v0.13.0 を使用しています.
また,もし間違い等ありましたらお知らせください.

概要

AWS CloudFormation では,AWS::Include transform を使用することで JSON / YAML ファイル等をインポートする(埋め込む)ことが可能です.

インポートできるファイルは予め S3 に配置されている必要がありますが,AWS CLI の aws cloudformation package や AWS SAM CLI の sam package コマンドを使用してパッケージングすることでローカルファイルの指定が可能になります.

よくある利用法としては,AWS SAM で Swagger / OpenAPI ファイルを読み込む際に AWS::Include transform を使用することで,Swagger / OpenAPI ファイル内で CloudFormation パラメータの参照等が可能になるというテクニックがあります.

参考:AWS CLIがAWS::Include transformをS3へアップロードできるようになりました | DevelopersIO

この AWS::Include ですが,ローカルファイルを相対パスで指定する場合,「テンプレートファイルからの相対パス」ではなく「package コマンドを使用したディレクトリからの相対パス」となっている(らしい)ことに注意する必要があります.

以下のような AWS SAM テンプレートを用意します.

root_template.yaml
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31

Resources:
  Child:
    Type: AWS::Serverless::Application
    Properties:
      Location: child/child_template.yaml

AWS::Serverless::Applicationchild/ ディレクトリ内の child_template.yaml を指定しています.

AWS::Serverless::Application でテンプレートを指定すると, AWS::CloudFormation::Stack と同様にネストされたスタックとして作成されます.

child_template.yaml では,同じく child/ ディレクトリ内にある openapi.yamlAWS::Include によって指定しています.

child/child_template.yaml
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31

Resources:
  SampleFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: lambda/
      Handler: app.lambda_handler
      Runtime: python3.7

  SampleAPI:
    Type: AWS::Serverless::Api
    Properties:
      Name: Sample
      StageName: dev
      DefinitionBody:
        Fn::Transform:
          Name: AWS::Include
          Parameters:
            Location: openapi.yaml

  SampleFunctionPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !Ref SampleFunction
      Principal: apigateway.amazonaws.com
child/openapi.yaml
openapi: 3.0.1

info:
  title: Sample API
  version: '2019-03-22'

paths:
  /sample:
    get:
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object

      x-amazon-apigateway-integration:
        uri:
          Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${SampleFunction.Arn}/invocations
        responses:
          default:
            statusCode: '200'
        passthroughBehavior: when_no_match
        httpMethod: POST
        contentHandling: CONVERT_TO_TEXT
        type: aws_proxy

では, root_template.yaml と同じディレクトリに戻ってパッケージングしてみましょう.

sam package --template-file root_template.yaml --output-template-file packaged.yaml --s3-bucket nest.include.sample

パッケージングにより S3 に生成されたファイルのうち,子テンプレートである child/child_template.yaml ファイルが変換されたものを見てみると,次のようになっていることがわかります.

AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Resources:
  SampleFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: s3://nest.include.sample/9b592c85ac8dde7c747072349d5a4db6
      Handler: app.lambda_handler
      Runtime: python3.7
  SampleAPI:
    Type: AWS::Serverless::Api
    Properties:
      Name: Sample
      StageName: dev
      DefinitionBody:
        Fn::Transform:
          Name: AWS::Include
          Parameters:
            Location: openapi.yaml # <- 変換されていない!
  SampleFunctionPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName:
        Ref: SampleFunction
      Principal: apigateway.amazonaws.com

SampleAPIDefinitionBody に注目すると,AWS::Include で指定した openapi.yaml ファイルが正しく S3 に展開されていないことがわかります.

原因

AWS::IncludeLocation パラメータに指定する相対パスは,「package コマンドを使用したディレクトリからの」相対パスである必要があるようです.

例えば今回のように root_template.yaml と同じディレクトリから sam package コマンドを利用する場合,

child/child_template.yaml
...

  SampleAPI:
    Type: AWS::Serverless::Api
    Properties:
      Name: Sample
      StageName: dev
      DefinitionBody:
        Fn::Transform:
          Name: AWS::Include
          Parameters:
            Location: child/openapi.yaml # <- ココ

...

のように指定してあげると正しくパッケージングされます.

この仕様は AWS::Serverless::FunctionCodeUri(そのテンプレートファイルからの相対パスを指定する)等とは異なるため,混乱しないように注意が必要です.

また,package コマンドを実行するディレクトリが一定でないような場合も注意が必要です.

この問題に関してはこちらの Issue でも議論されているようです.

cloudformation package uses a different root directory for include locations and CodeUri · Issue #3786 · aws/aws-cli

まとめ

AWS::Include でローカルファイルを相対パスで指定するときは,package コマンドを実行するディレクトリからの相対パスにする!

CyLomw
中小 IT で研究員したりプログラム書いたり.
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした