

# Lambda 지원 사용자 지정 리소스
<a name="template-custom-resources-lambda"></a>

Lambda 함수를 사용자 지정 리소스와 연결하면 해당 사용자 지정 리소스가 생성, 업데이트 또는 삭제될 때마다 이 함수가 호출됩니다. CloudFormation은 Lambda API를 호출해 이러한 함수를 호출하고 모든 요청 데이터(예: 요청 유형 및 리소스 속성)를 이 함수로 전달합니다. CloudFormation과 함께 Lambda 함수의 이점 및 사용자 지정 가능성 덕분에 스택 생성 중 동적으로 AMI ID 조회 또는 유틸리티 함수(예: 문자열 반전 함수) 구현 및 사용과 같은 광범위한 시나리오를 처리할 수 있습니다.

사용자 지정 리소스의 소개와 작동 방식은 [사용자 지정 리소스가 작동하는 방식](template-custom-resources.md#how-custom-resources-work) 섹션을 참조하세요.

**Topics**
+ [연습: Lambda 지원 사용자 지정 리소스를 사용하여 지연 메커니즘 생성](walkthrough-lambda-backed-custom-resources.md)
+ [`cfn-response` 모듈](cfn-lambda-function-code-cfnresponsemodule.md)

# 연습: Lambda 지원 사용자 지정 리소스를 사용하여 지연 메커니즘 생성
<a name="walkthrough-lambda-backed-custom-resources"></a>

이 연습에서는 샘플 CloudFormation 템플릿을 사용하여 Lambda 지원 사용자 지정 리소스를 구성하고 시작하는 방법을 보여줍니다. 이 템플릿은 지정된 시간 동안 스택 배포를 일시 중지하는 지연 메커니즘을 생성합니다. 이는 종속 리소스가 생성되기 전에 리소스가 안정화될 때까지 기다리는 경우와 같이 리소스 프로비저닝 중에 의도적으로 지연시켜야 하는 경우에 유용할 수 있습니다.

**참고**  
이전에는 AMI ID를 검색하는 데 Lambda 지원 사용자 지정 리소스가 권장되었지만 이제 AWS Systems Manager 파라미터를 사용하는 것이 좋습니다. 이러한 접근 방식을 통해 더 쉽게 템플릿을 재사용하고 유지 관리할 수 있습니다. 자세한 내용은 [Systems Manager Parameter Store에서 일반 텍스트 값 가져오기](dynamic-references-ssm.md) 섹션을 참조하세요.

**Topics**
+ [개요](#walkthrough-lambda-backed-custom-resources-overview)
+ [샘플 템플릿](#walkthrough-lambda-backed-custom-resources-sample-template)
+ [샘플 템플릿 연습](#walkthrough-lambda-backed-custom-resources-sample-template-walkthrough)
+ [사전 조건](#walkthrough-lambda-backed-custom-resources-prerequisites)
+ [스택 시작](#walkthrough-lambda-backed-custom-resources-createfunction-createstack)
+ [리소스 정리](#walkthrough-lambda-backed-custom-resources-createfunction-cleanup)
+ [관련 정보](#w2aac11c45b9c24b9c23)

## 개요
<a name="walkthrough-lambda-backed-custom-resources-overview"></a>

이 연습에서 사용된 샘플 스택 템플릿은 Lambda 지원 사용자 지정 리소스를 생성합니다. 이 사용자 지정 리소스는 스택 생성 중에 구성 가능한 지연(기본값 60초)을 도입합니다. 지연은 사용자 지정 리소스의 속성이 수정되는 경우에만 스택 업데이트 중에 발생합니다.

템플릿은 다음 리소스를 프로비저닝합니다.
+ 사용자 지정 리소스,
+ Lambda 함수, 및
+ Lambda가 CloudWatch에 로그를 작성할 수 있도록 하는 IAM 역할.

이는 또한 다음 두 가지 출력도 정의합니다.
+ 함수가 대기한 실제 시간입니다.
+ Lambda 함수의 각 실행 동안 생성되는 고유 식별자입니다.



**참고**  
CloudFormation은 무료 서비스이지만 Lambda는 함수에 대한 요청 수 및 코드 실행 시간에 따라 요금을 부과합니다. Lambda 요금에 대한 자세한 내용은 [AWS Lambda 요금](https://aws.amazon.com/lambda/pricing/)을 참조하세요.

## 샘플 템플릿
<a name="walkthrough-lambda-backed-custom-resources-sample-template"></a>

아래 지연 메커니즘을 사용하여 Lambda 지원 사용자 지정 리소스 샘플 템플릿을 확인할 수 있습니다.

### JSON
<a name="walkthrough-lambda-backed-custom-resources-sample-template-json"></a>

```
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Resources": {
    "LambdaExecutionRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Statement": [{
            "Effect": "Allow",
            "Principal": { "Service": ["lambda.amazonaws.com"] },
            "Action": ["sts:AssumeRole"]
          }]
        },
        "Path": "/",
        "Policies": [{
          "PolicyName": "AllowLogs",
          "PolicyDocument": {
            "Statement": [{
              "Effect": "Allow",
              "Action": ["logs:*"],
              "Resource": "*"
            }]
          }
        }]
      }
    },
    "CFNWaiter": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Handler": "index.handler",
        "Runtime": "python3.9",
        "Timeout": 900,
        "Role": { "Fn::GetAtt": ["LambdaExecutionRole", "Arn"] },
        "Code": {
          "ZipFile": { "Fn::Join": ["\n", [
            "from time import sleep",
            "import json",
            "import cfnresponse",
            "import uuid",
            "",
            "def handler(event, context):",
            "  wait_seconds = 0",
            "  id = str(uuid.uuid1())",
            "  if event[\"RequestType\"] in [\"Create\", \"Update\"]:",
            "    wait_seconds = int(event[\"ResourceProperties\"].get(\"ServiceTimeout\", 0))",
            "    sleep(wait_seconds)",
            "  response = {",
            "    \"TimeWaited\": wait_seconds,",
            "    \"Id\": id ",
            "  }",
            "  cfnresponse.send(event, context, cfnresponse.SUCCESS, response, \"Waiter-\"+id)"
          ]]}
        }
      }
    },
    "CFNWaiterCustomResource": {
      "Type": "AWS::CloudFormation::CustomResource",
      "Properties": {
        "ServiceToken": { "Fn::GetAtt": ["CFNWaiter", "Arn"] },
        "ServiceTimeout": 60
      }
    }
  },
  "Outputs": {
    "TimeWaited": {
      "Value": { "Fn::GetAtt": ["CFNWaiterCustomResource", "TimeWaited"] },
      "Export": { "Name": "TimeWaited" }
    },
    "WaiterId": {
      "Value": { "Fn::GetAtt": ["CFNWaiterCustomResource", "Id"] },
      "Export": { "Name": "WaiterId" }
    }
  }
}
```

### YAML
<a name="walkthrough-lambda-backed-custom-resources-sample-template-yaml"></a>

```
AWSTemplateFormatVersion: "2010-09-09"
Resources:
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - "lambda.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      Path: "/"
      Policies:
        - PolicyName: "AllowLogs"
          PolicyDocument:
            Statement:
              - Effect: "Allow"
                Action:
                  - "logs:*"
                Resource: "*"
  CFNWaiter:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.handler
      Runtime: python3.9 
      Timeout: 900
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile:
          !Sub |
          from time import sleep
          import json
          import cfnresponse
          import uuid
​
          def handler(event, context):
            wait_seconds = 0
            id = str(uuid.uuid1())
            if event["RequestType"] in ["Create", "Update"]:
              wait_seconds = int(event["ResourceProperties"].get("ServiceTimeout", 0))
              sleep(wait_seconds)
            response = {
              "TimeWaited": wait_seconds,
              "Id": id 
            }
            cfnresponse.send(event, context, cfnresponse.SUCCESS, response, "Waiter-"+id)
  CFNWaiterCustomResource:
    Type: AWS::CloudFormation::CustomResource
    Properties:
      ServiceToken: !GetAtt CFNWaiter.Arn
      ServiceTimeout: 60
Outputs:
  TimeWaited:
    Value: !GetAtt CFNWaiterCustomResource.TimeWaited
    Export:
      Name: TimeWaited
  WaiterId:
    Value: !GetAtt CFNWaiterCustomResource.Id
    Export:
      Name: WaiterId
```

## 샘플 템플릿 연습
<a name="walkthrough-lambda-backed-custom-resources-sample-template-walkthrough"></a>

다음 코드 조각은 샘플 템플릿의 관련 부분을 설명하여 Lambda 함수를 사용자 지정 리소스와 연결하는 방법과 해당 출력을 이해하도록 도움이 됩니다.

[AWS::Lambda::Function](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-lambda-function.html) 리소스 `CFNWaiter`  
`AWS::Lambda::Function` 리소스는 함수의 소스 코드, 핸들러 이름, 런타임 환경 및 실행 역할 Amazon 리소스 이름(ARN)을 지정합니다.  
`Handler` 속성은 Python 소스 코드를 사용하기 때문에 `index.handler`로 설정됩니다. 인라인 함수 소스 코드를 사용할 때 허용되는 핸들러 식별자에 대한 자세한 내용은 [AWS::Lambda::Function Code](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html#cfn-lambda-function-code-zipfile)를 참조하세요.  
`Runtime`은 소스 파일이 Python 코드이므로는 `python3.9`로 지정됩니다.  
`Timeout`은 900초로 설정됩니다.  
`Role` 속성은 `Fn::GetAtt` 함수를 사용하여 템플릿의 `AWS::IAM::Role` 리소스에 선언된 `LambdaExecutionRole` 실행 역할의 ARN을 가져옵니다.  
`Code` 속성은 Python 함수를 사용하여 함수 코드를 인라인으로 정의합니다. 샘플 템플릿의 Python 함수는 다음을 수행합니다.  
+ UUID를 사용하여 고유 ID 생성
+ 요청이 생성 요청인지 또는 업데이트 요청인지 확인
+ `Create` 또는 `Update` 요청 중 `ServiceTimeout`에 지정된 기간만큼 대기
+ 대기 시간 및 고유 ID 반환

### JSON
<a name="walkthrough-lambda-backed-custom-resources-sample-template-lambda-resource-json"></a>

```
...
    "CFNWaiter": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Handler": "index.handler",
        "Runtime": "python3.9",
        "Timeout": 900,
        "Role": { "Fn::GetAtt": ["LambdaExecutionRole", "Arn"] },
        "Code": {
          "ZipFile": { "Fn::Join": ["\n", [
            "from time import sleep",
            "import json",
            "import cfnresponse",
            "import uuid",
            "",
            "def handler(event, context):",
            "  wait_seconds = 0",
            "  id = str(uuid.uuid1())",
            "  if event[\"RequestType\"] in [\"Create\", \"Update\"]:",
            "    wait_seconds = int(event[\"ResourceProperties\"].get(\"ServiceTimeout\", 0))",
            "    sleep(wait_seconds)",
            "  response = {",
            "    \"TimeWaited\": wait_seconds,",
            "    \"Id\": id ",
            "  }",
            "  cfnresponse.send(event, context, cfnresponse.SUCCESS, response, \"Waiter-\"+id)"
          ]]}
        }
      }
    },
...
```

### YAML
<a name="walkthrough-lambda-backed-custom-resources-sample-template-lambda-resource-yaml"></a>

```
...
  CFNWaiter:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.handler
      Runtime: python3.9 
      Timeout: 900
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile:
          !Sub |
          from time import sleep
          import json
          import cfnresponse
          import uuid
​
          def handler(event, context):
            wait_seconds = 0
            id = str(uuid.uuid1())
            if event["RequestType"] in ["Create", "Update"]:
              wait_seconds = int(event["ResourceProperties"].get("ServiceTimeout", 0))
              sleep(wait_seconds)
            response = {
              "TimeWaited": wait_seconds,
              "Id": id 
            }
            cfnresponse.send(event, context, cfnresponse.SUCCESS, response, "Waiter-"+id)
...
```

[AWS::IAM::Role](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-iam-role.html) 리소스 `LambdaExecutionRole`  
`AWS::IAM:Role` 리소스는 Lambda 함수의 실행 역할을 생성합니다. 여기에는 Lambda가 이를 사용할 수 있도록 허용하는 수임 역할 정책이 포함됩니다. 또한 CloudWatch Logs 액세스를 허용하는 정책도 포함됩니다.

### JSON
<a name="walkthrough-lambda-backed-custom-resources-sample-template-iam-role-json"></a>

```
...
    "LambdaExecutionRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Statement": [{
            "Effect": "Allow",
            "Principal": { "Service": ["lambda.amazonaws.com"] },
            "Action": ["sts:AssumeRole"]
          }]
        },
        "Path": "/",
        "Policies": [{
          "PolicyName": "AllowLogs",
          "PolicyDocument": {
            "Statement": [{
              "Effect": "Allow",
              "Action": ["logs:*"],
              "Resource": "*"
            }]
          }
        }]
      }
    },
...
```

### YAML
<a name="walkthrough-lambda-backed-custom-resources-sample-template-iam-role-yaml"></a>

```
...
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - "lambda.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      Path: "/"
      Policies:
        - PolicyName: "AllowLogs"
          PolicyDocument:
            Statement:
              - Effect: "Allow"
                Action:
                  - "logs:*"
                Resource: "*"
...
```

[AWS::CloudFormation::CustomResource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudformation-customresource.html) 리소스 `CFNWaiterCustomResource`  
사용자 지정 리소스는 `!GetAtt CFNWaiter.Arn`을 사용하여 ARN으로 Lambda 함수에 연결됩니다. `ServiceTimeout`에 설정된 대로 생성 및 업데이트 작업에 대해 60초의 대기 시간을 구현합니다. 속성이 수정된 경우에만 업데이트 작업에 대해서만 리소스가 간접 호출됩니다.

### JSON
<a name="walkthrough-lambda-backed-custom-resources-sample-template-custom-resource-json"></a>

```
...
    "CFNWaiterCustomResource": {
      "Type": "AWS::CloudFormation::CustomResource",
      "Properties": {
        "ServiceToken": { "Fn::GetAtt": ["CFNWaiter", "Arn"] },
        "ServiceTimeout": 60
      }
    }
  },
...
```

### YAML
<a name="walkthrough-lambda-backed-custom-resources-sample-template-custom-resource-yaml"></a>

```
...
  CFNWaiterCustomResource:
    Type: AWS::CloudFormation::CustomResource
    Properties:
      ServiceToken: !GetAtt CFNWaiter.Arn
      ServiceTimeout: 60
...
```

`Outputs`  
이 템플릿의 `Outputs`은 `TimeWaited` 및 `WaiterId`입니다. `TimeWaited` 값은 `Fn::GetAtt` 함수를 사용하여 대기자 리소스가 실제로 대기한 시간을 제공합니다. `WaiterId`는 `Fn::GetAtt` 함수를 사용하여 생성되고 실행과 연결된 고유 ID를 제공합니다.

### JSON
<a name="walkthrough-lambda-backed-custom-resources-sample-template-output-json"></a>

```
...
  "Outputs": {
    "TimeWaited": {
      "Value": { "Fn::GetAtt": ["CFNWaiterCustomResource", "TimeWaited"] },
      "Export": { "Name": "TimeWaited" }
    },
    "WaiterId": {
      "Value": { "Fn::GetAtt": ["CFNWaiterCustomResource", "Id"] },
      "Export": { "Name": "WaiterId" }
    }
  }
}
...
```

### YAML
<a name="walkthrough-lambda-backed-custom-resources-sample-template-output-yaml"></a>

```
...
Outputs:
  TimeWaited:
    Value: !GetAtt CFNWaiterCustomResource.TimeWaited
    Export:
      Name: TimeWaited
  WaiterId:
    Value: !GetAtt CFNWaiterCustomResource.Id
    Export:
      Name: WaiterId
...
```

## 사전 조건
<a name="walkthrough-lambda-backed-custom-resources-prerequisites"></a>

해당하는 서비스(예: Lambda 및 CloudFormation)를 모두 사용하려면 IAM 권한도 있어야 합니다.

## 스택 시작
<a name="walkthrough-lambda-backed-custom-resources-createfunction-createstack"></a>

**스택을 생성하는 방법**

1. [샘플 템플릿](#walkthrough-lambda-backed-custom-resources-sample-template) 섹션에서 원하는 템플릿(YAML 또는 JSON)을 찾아 컴퓨터에 `samplelambdabackedcustomresource.template` 이름으로 저장하세요.

1. [https://console.aws.amazon.com/cloudformation/](https://console.aws.amazon.com/cloudformation/)에서 CloudFormation 콘솔을 엽니다.

1. **스택** 페이지의 오른쪽 상단에서 **스택 생성**을 선택하고 **새 리소스 사용(표준)**을 선택합니다.

1. **사전 조건 - 템플릿 준비**에서 **기존 템플릿 선택**을 선택합니다.

1. **템플릿 지정**에서 **템플릿 파일 업로드**를 선택한 다음 **파일 선택**을 선택하세요.

1. 이전에 저장한 `samplelambdabackedcustomresource.template` 템플릿 파일을 선택하세요.

1. **다음**을 선택합니다.

1. **스택 이름**에 **SampleCustomResourceStack**을 입력하고 **다음**을 선택하세요.

1. 이 연습에서는 태그를 추가하거나 고급 설정을 지정할 필요가 없으므로 **다음**을 선택합니다.

1. 스택 이름이 올바른지 확인한 다음 **업데이트**를 선택하세요.

CloudFormation에서 스택을 생성하는 데 몇 분 정도 걸릴 수 있습니다. 진행 상황을 모니터링하려면 스택 이벤트를 확인합니다. 자세한 내용은 [CloudFormation 콘솔에서 스택 정보 보기](cfn-console-view-stack-data-resources.md) 섹션을 참조하세요.

스택 생성에 성공하면 스택의 모든 리소스(예: Lambda 함수 및 사용자 지정 리소스)가 생성됩니다. Lambda 함수 및 사용자 지정 리소스를 성공적으로 사용했습니다.

Lambda 함수가 오류를 반환하면 CloudWatch Logs [콘솔](https://console.aws.amazon.com/cloudwatch/home#logs:)에서 함수 로그를 확인합니다. 로그 스트림의 이름은 사용자 지정 리소스의 물리적 ID로, 스택의 리소스를 보면 확인할 수 있습니다. 자세한 내용을 알아보려면 Amazon CloudWatch 사용 설명서의 [로그 데이터 보기](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/Working-with-log-groups-and-streams.html#ViewingLogData)를 참조하세요.**

## 리소스 정리
<a name="walkthrough-lambda-backed-custom-resources-createfunction-cleanup"></a>

스택을 삭제해 생성한 스택 리소스를 모두 정리하면 불필요한 리소스에 대해 비용이 청구되지 않습니다.

**스택을 삭제하려면**

1. CloudFormation 콘솔에서 **SampleCustomResourceStack** 스택을 선택하세요.

1. **작업**을 선택한 다음 **스택 삭제**를 선택합니다.

1. 확인 메시지에서 **예, 삭제**를 선택합니다.

생성한 리소스가 모두 삭제됩니다.

Lambda 지원 사용자 지정 리소스를 생성하고 사용하는 방법을 이해했으므로 이 연습의 샘플 템플릿과 코드를 사용하여 기타 스택 및 함수를 빌드하고 이를 사용해 실험할 수 있습니다.

## 관련 정보
<a name="w2aac11c45b9c24b9c23"></a>
+ [CloudFormation 사용자 지정 리소스 참조](crpg-ref.md)
+ [AWS::CloudFormation::CustomResource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudformation-customresource.html)

# `cfn-response` 모듈
<a name="cfn-lambda-function-code-cfnresponsemodule"></a>

CloudFormation 템플릿에서는 사용자 지정 리소스의 대상으로 Lambda 함수를 지정할 수 있습니다. `ZipFile` 속성을 사용하여 [함수](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-lambda-function.html)의 소스 코드를 지정하는 경우 Lambda 함수에서 사용자 지정 리소스로 응답을 전송할 `cfn-response` 모듈을 로드할 수 있습니다. `cfn-response` 모듈은 Lambda 함수를 호출한 사용자 지정 리소스로의 응답 전송을 간소화하는 라이브러리입니다. 이 모듈에는 `send` 메서드가 있고, 이 메서드는 Amazon S3 미리 서명된 URL(`ResponseURL`)을 사용하여 [응답 객체](crpg-ref.md#crpg-ref-responses)를 사용자 지정 리소스로 전송합니다.

`cfn-response` 모듈은 `ZipFile` 속성을 사용하여 소스 코드를 작성할 때만 사용할 수 있습니다. Amazon S3 버킷에 저장된 소스 코드에 사용할 수 없습니다. 버킷 내 코드의 경우, 응답을 전송할 고유 함수를 작성해야 합니다.

**참고**  
`send` 메서드를 실행하고 나면 Lambda 함수가 종료하므로 해당 메서드 이후에 작성하는 모든 내용이 무시됩니다.

## `cfn-response` 모듈 로드
<a name="cfn-lambda-function-code-cfnresponsemodule-loading"></a>

Node.js 함수의 경우 `require()` 함수를 사용하여 `cfn-response` 모듈을 불러옵니다. 예를 들면 다음 코드 예제에서는 `cfn-response` 이름의 `response` 객체를 생성합니다.

```
var response = require('cfn-response');
```

Python의 경우 다음 예제에서와 같이 `import` 문을 사용하여 `cfnresponse` 모듈을 로드합니다.

**참고**  
이 정확한 가져오기 문을 사용합니다. 가져오기 문의 다양한 변형을 사용하는 경우 CloudFormation에서 응답 모듈을 포함하지 않습니다.

```
import cfnresponse
```

## `send` 메서드 파라미터
<a name="cfn-lambda-function-code-cfnresponsemodule-send-parameters"></a>

`send` 메서드와 함께 다음 파라미터를 사용할 수 있습니다.

`event`  
[사용자 지정 리소스 요청](crpg-ref.md#crpg-ref-requesttypes)의 필드입니다.

`context`  
함수 및 콜백이 Lambda 실행 환경 내에서 정보에 액세스했거나 실행을 완료한 경우를 지정하는 데 사용할 수 있는 Lambda 함수 관련 객체입니다. 자세한 내용은 *AWS Lambda 개발자 안내서*의 [Node.js를 사용하여 Lambda 함수 빌드](https://docs.aws.amazon.com/lambda/latest/dg/lambda-nodejs.html)를 참조하세요.

`responseStatus`  
함수가 성공적으로 완료되었는지 여부를 나타냅니다. `cfnresponse` 모듈 제약을 사용하여 실행 성공에 대해 `SUCCESS`를 지정하고 실행 실패에 대해 `FAILED`를 지정합니다.

`responseData`  
사용자 지정 [응답 객체](crpg-ref.md#crpg-ref-responses)의 `Data` 필드입니다. 데이터는 이름-값 페어 목록입니다.

`physicalResourceId`  
선택 사항. 함수를 호출한 사용자 지정 리소스의 고유 식별자입니다. 기본적으로 이 모듈은 Lambda 함수와 연결된 Amazon CloudWatch Logs 로그 스트림의 이름을 사용합니다.  
`PhysicalResourceId`에 대해 반환된 값은 사용자 지정 리소스 업데이트 작업을 변경할 수 있습니다. 반환된 값이 같은 경우 일반 업데이트로 간주됩니다. 반환된 값이 다른 경우 CloudFormation은 업데이트를 교체로 인식하고 기존 리소스에 삭제 요청을 전송합니다. 자세한 내용은 [https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-cloudformation-customresource.html](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-cloudformation-customresource.html) 섹션을 참조하세요.

`noEcho`  
선택 사항. `Fn::GetAtt` 함수를 사용하여 조회할 때 사용자 지정 리소스의 출력을 마스킹할지 여부를 나타냅니다. `true`로 설정하면 아래 지정된 위치에 저장된 정보를 제외하고, 반환된 모든 값은 별표(\$1\$1\$1\$1\$1)로 마스킹됩니다. 기본적으로 이 값은 `false`입니다.  
`NoEcho` 속성을 사용해도 다음에 저장된 정보는 마스킹되지 않습니다.  
+ `Metadata` 템플릿 섹션. CloudFormation은 `Metadata` 섹션에 포함된 정보를 변환, 수정 또는 삭제하지 않습니다. 자세한 내용은 [메타데이터](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/metadata-section-structure.html)를 참조하십시오.
+ `Outputs` 템플릿 섹션. 자세한 내용은 [출력](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html)을 참조하십시오.
+ 리소스 정의의 `Metadata` 속성입니다. 자세한 내용은 [`Metadata` 속성](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-attribute-metadata.html)을 참조하세요.
이러한 메커니즘을 사용하여 암호나 보안 정보와 같은 중요한 정보를 포함하지 않는 것이 좋습니다.
`NoEcho`를 사용하여 민감한 정보를 마스킹 처리하는 방법에 대한 자세한 내용은 [템플릿에 자격 증명을 포함하지 않음](security-best-practices.md#creds) 모범 사례를 참조하세요.

## 예제
<a name="cfn-lambda-function-code-cfnresponsemodule-examples"></a>

### Node.js
<a name="cfn-lambda-function-code-zipfile-examplenodejs"></a>

다음 Node.js 예제에서 인라인 Lambda 함수는 입력 값을 받아서 그 값에 5를 곱합니다. 인라인 함수를 사용하면 패키지를 생성하여 Amazon S3 버킷에 업로드하는 대신에 템플릿에서 소스 코드를 직접 지정할 수 있으므로 인라인 함수는 작은 함수에 특히 유용합니다. 이 함수는 `cfn-response` `send` 메서드를 사용하여 호출했던 사용자 지정 리소스로 결과를 다시 전송합니다.

#### JSON
<a name="cfn-lambda-function-code-zipfile-examplenodejs.json"></a>

```
"ZipFile": { "Fn::Join": ["", [
  "var response = require('cfn-response');",
  "exports.handler = function(event, context) {",
  "  var input = parseInt(event.ResourceProperties.Input);",
  "  var responseData = {Value: input * 5};",
  "  response.send(event, context, response.SUCCESS, responseData);",
  "};"
]]}
```

#### YAML
<a name="cfn-lambda-function-code-zipfile-examplenodejs-yaml"></a>

```
ZipFile: >
  var response = require('cfn-response');
  exports.handler = function(event, context) {
    var input = parseInt(event.ResourceProperties.Input);
    var responseData = {Value: input * 5};
    response.send(event, context, response.SUCCESS, responseData);
  };
```

### Python
<a name="cfn-lambda-function-code-zipfile-examplepython"></a>

다음 Python 예제에서 인라인 Lambda 함수는 정수 값을 받아서 그 값에 5를 곱합니다.

#### JSON
<a name="cfn-lambda-function-code-zipfile-examplepython.json"></a>

```
"ZipFile" : { "Fn::Join" : ["\n", [
  "import json",
  "import cfnresponse",
  "def handler(event, context):",
  "   responseValue = int(event['ResourceProperties']['Input']) * 5",
  "   responseData = {}",
  "   responseData['Data'] = responseValue",
  "   cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, \"CustomResourcePhysicalID\")"
]]}
```

#### YAML
<a name="cfn-lambda-function-code-zipfile-examplepython.yaml"></a>

```
ZipFile: |
  import json
  import cfnresponse
  def handler(event, context):
    responseValue = int(event['ResourceProperties']['Input']) * 5
    responseData = {}
    responseData['Data'] = responseValue
    cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, "CustomResourcePhysicalID")
```

## 모듈 소스 코드
<a name="cfn-lambda-function-code-cfnresponsemodule-source"></a>

**Topics**
+ [비동기 Node.js 소스 코드](#cfn-lambda-function-code-cfnresponsemodule-source-nodejs-async)
+ [Node.js 소스 코드](#cfn-lambda-function-code-cfnresponsemodule-source-nodejs)
+ [Python 소스 코드](#cfn-lambda-function-code-cfnresponsemodule-source-python)

### 비동기 Node.js 소스 코드
<a name="cfn-lambda-function-code-cfnresponsemodule-source-nodejs-async"></a>

다음은 핸들러가 비동기식인 경우의 Node.js 함수에 대한 응답 모듈 소스 코드입니다. 이 코드를 검토하면 모듈의 기능을 이해할 수 있으며 고유의 응답 함수를 구현하는 데 도움이 됩니다.

```
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0

exports.SUCCESS = "SUCCESS";
exports.FAILED = "FAILED";

exports.send = function(event, context, responseStatus, responseData, physicalResourceId, noEcho) {

    return new Promise((resolve, reject) => {
        var responseBody = JSON.stringify({
            Status: responseStatus,
            Reason: "See the details in CloudWatch Log Stream: " + context.logStreamName,
            PhysicalResourceId: physicalResourceId || context.logStreamName,
            StackId: event.StackId,
            RequestId: event.RequestId,
            LogicalResourceId: event.LogicalResourceId,
            NoEcho: noEcho || false,
            Data: responseData
        });

        console.log("Response body:\n", responseBody);

        var https = require("https");
        var url = require("url");

        var parsedUrl = url.parse(event.ResponseURL);
        var options = {
            hostname: parsedUrl.hostname,
            port: 443,
            path: parsedUrl.path,
            method: "PUT",
            headers: {
                "content-type": "",
                "content-length": responseBody.length
            }
        };

        var request = https.request(options, function(response) {
            console.log("Status code: " + parseInt(response.statusCode));
            resolve(context.done());
        });

        request.on("error", function(error) {
            console.log("send(..) failed executing https.request(..): " + maskCredentialsAndSignature(error));
            reject(context.done(error));
        });

        request.write(responseBody);
        request.end();
    })
}
 
function maskCredentialsAndSignature(message) {
    return message.replace(/X-Amz-Credential=[^&\s]+/i, 'X-Amz-Credential=*****')
        .replace(/X-Amz-Signature=[^&\s]+/i, 'X-Amz-Signature=*****');
}
```

### Node.js 소스 코드
<a name="cfn-lambda-function-code-cfnresponsemodule-source-nodejs"></a>

다음은 핸들러가 비동기식이 아닌 경우의 Node.js 함수에 대한 응답 모듈 소스 코드입니다. 이 코드를 검토하면 모듈의 기능을 이해할 수 있으며 고유의 응답 함수를 구현하는 데 도움이 됩니다.

```
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0
 
exports.SUCCESS = "SUCCESS";
exports.FAILED = "FAILED";

exports.send = function(event, context, responseStatus, responseData, physicalResourceId, noEcho) {

    var responseBody = JSON.stringify({
        Status: responseStatus,
        Reason: "See the details in CloudWatch Log Stream: " + context.logStreamName,
        PhysicalResourceId: physicalResourceId || context.logStreamName,
        StackId: event.StackId,
        RequestId: event.RequestId,
        LogicalResourceId: event.LogicalResourceId,
        NoEcho: noEcho || false,
        Data: responseData
    });

    console.log("Response body:\n", responseBody);

    var https = require("https");
    var url = require("url");

    var parsedUrl = url.parse(event.ResponseURL);
    var options = {
        hostname: parsedUrl.hostname,
        port: 443,
        path: parsedUrl.path,
        method: "PUT",
        headers: {
            "content-type": "",
            "content-length": responseBody.length
        }
    };

    var request = https.request(options, function(response) {
        console.log("Status code: " + parseInt(response.statusCode));
        context.done();
    });

    request.on("error", function(error) {
        console.log("send(..) failed executing https.request(..): " + maskCredentialsAndSignature(error));
        context.done();
    });

    request.write(responseBody);
    request.end();
}
```

### Python 소스 코드
<a name="cfn-lambda-function-code-cfnresponsemodule-source-python"></a>

다음은 Python 함수에 대한 응답 모듈 소스 코드입니다.

```
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0
 
from __future__ import print_function
import urllib3
import json
import re

SUCCESS = "SUCCESS"
FAILED = "FAILED"

http = urllib3.PoolManager()


def send(event, context, responseStatus, responseData, physicalResourceId=None, noEcho=False, reason=None):
    responseUrl = event['ResponseURL']

    responseBody = {
        'Status' : responseStatus,
        'Reason' : reason or "See the details in CloudWatch Log Stream: {}".format(context.log_stream_name),
        'PhysicalResourceId' : physicalResourceId or context.log_stream_name,
        'StackId' : event['StackId'],
        'RequestId' : event['RequestId'],
        'LogicalResourceId' : event['LogicalResourceId'],
        'NoEcho' : noEcho,
        'Data' : responseData
    }

    json_responseBody = json.dumps(responseBody)

    print("Response body:")
    print(json_responseBody)

    headers = {
        'content-type' : '',
        'content-length' : str(len(json_responseBody))
    }

    try:
        response = http.request('PUT', responseUrl, headers=headers, body=json_responseBody)
        print("Status code:", response.status)


    except Exception as e:

        print("send(..) failed executing http.request(..):", mask_credentials_and_signature(e))
 
 
def mask_credentials_and_signature(message):
    message = re.sub(r'X-Amz-Credential=[^&\s]+', 'X-Amz-Credential=*****', message, flags=re.IGNORECASE)
    return re.sub(r'X-Amz-Signature=[^&\s]+', 'X-Amz-Signature=*****', message, flags=re.IGNORECASE)
```