---
# System prepended metadata

title: SAM 專案多環境部署
tags: [chatgpt]

---

# SAM 專案多環境部署

[TOC]

###### tags: `chatgpt`

---

## 有個基於 aws-serverless-springboot2-archetype 的 SAM 專案，佈署到 AWS 時想要用 ImageUri，但在本地開發測試時想用 CodeUri，是否可共用一份 template.yml？

可以使用一份 `template.yml` 文件，並根據需要在不同的環境中設置不同的參數值。具體來說，您可以在 `template.yml` 中定義兩個不同的參數，例如 `CodeUri` 和 `ImageUri`，然後在您的 SAM 應用程序中使用這些參數。

以下是一個示例 `template.yml` 文件，其中包含 `CodeUri` 和 `ImageUri` 參數的定義：

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Parameters:
  CodeUri:
    Type: String
    Default: ./target/my-app-1.0.jar
  ImageUri:
    Type: String
    Default: 123456789012.dkr.ecr.us-west-2.amazonaws.com/my-app:latest

Resources:
  MyFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: !Ref CodeUri
      ImageUri: !Ref ImageUri
      Handler: com.example.MyFunctionHandler
      Runtime: java8
```

在此範例中，`CodeUri` 和 `ImageUri` 參數都被定義為字串類型，並分別設置了默認值。在您的 SAM 應用程序中，您可以使用以下命令來佈署應用程序：

```bash
# 執行本地部署，使用本地的 CodeUri
sam local start-api --parameter-overrides CodeUri=./target/my-app-1.0.jar

# 佈署到 AWS，使用 ImageUri
sam deploy --stack-name mystack --parameter-overrides ImageUri=123456789012.dkr.ecr.us-west-2.amazonaws.com/my-app:1.0
```

在本地開發時，使用 `sam local` 命令行工具啟動本地 API 網關，並使用 `--parameter-overrides` 選項來覆蓋 `CodeUri` 參數的值。在佈署到 AWS 時，使用 `sam deploy` 命令行工具，並同樣使用 `--parameter-overrides` 選項來覆蓋 `ImageUri` 參數的值。

## 能利用 `Condition` 來自動辨識嗎

是的，您可以使用 `Conditions` 在 `template.yml` 文件中自動辨識要使用的 URI，並相應地部署應用程序。具體來說，您可以在 `Conditions` 中定義一個條件，根據此條件來選擇要使用的 URI，然後在 `Resources` 中使用 `Fn::If` 函数來選擇要使用的 URI。

以下是一個示例 `template.yml` 文件，其中使用了 `Conditions` 來自動辨識要使用的 URI：

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Conditions:
  UseImageUri: !Not [!Equals [!Ref ImageUri, ""]]

Parameters:
  CodeUri:
    Type: String
    Default: ./target/my-app-1.0.jar
  ImageUri:
    Type: String
    Default: ""

Resources:
  MyFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: !If [UseImageUri, "", !Ref CodeUri]
      ImageUri: !If [UseImageUri, !Ref ImageUri, ""]
      Handler: com.example.MyFunctionHandler
      Runtime: java8
```

在此範例中，我們定義了一個名為 `UseImageUri` 的條件，該條件將判斷 `ImageUri` 參數是否被設置為空。如果 `ImageUri` 參數不為空，則 `UseImageUri` 條件將為 `false`，否則為 `true`。

在 `Resources` 中，我們使用了 `Fn::If` 函数來選擇要使用的 URI。如果 `UseImageUri` 條件為 `true`，則使用 `ImageUri` 參數作為 `ImageUri` 屬性的值，並將 `CodeUri` 屬性的值設置為空字符串。如果 `UseImageUri` 條件為 `false`，則使用 `CodeUri` 參數作為 `CodeUri` 屬性的值，並將 `ImageUri` 屬性的值設置為空字符串。

使用此方法，您可以在部署應用程序時自動辨識要使用的 URI，而不需要手動覆蓋參數。

## 首先，你的範例並未配置 API Gateway；再者，在不修改 template.yml 內容的狀況下，該條件如何判斷 ImageUri 參數是否被設置為空？

非常抱歉我在先前的回答中有誤，您是對的，我的範例確實未配置 API Gateway。

此外，在不修改 `template.yml` 內容的情況下，條件本身並不能判斷 `ImageUri` 參數是否被設置為空。如果您想在運行時判斷 `ImageUri` 參數是否為空，您可以在 Lambda 函數代碼中使用環境變數來傳遞此信息。具體來說，您可以在 Lambda 函數代碼中讀取環境變數，然後根據變數的值來決定要使用的 URI。

以下是一個示例 Lambda 函數代碼，使用環境變數來判斷要使用的 URI：

```java
package com.example;

import java.io.InputStream;
import java.io.OutputStream;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;

public class MyFunctionHandler implements RequestStreamHandler {

	private static final String USE_IMAGE_URI_ENV_VAR = "USE_IMAGE_URI";

	public void handleRequest(InputStream input, OutputStream output, Context context) {
		boolean useImageUri = Boolean.parseBoolean(System.getenv(USE_IMAGE_URI_ENV_VAR));
		String uri = useImageUri ? System.getenv("IMAGE_URI") : System.getenv("CODE_URI");
		// TODO: Use the URI to execute the desired logic
	}
}
```

在此範例中，我們使用 `System.getenv` 函數來讀取 `USE_IMAGE_URI` 環境變數，然後根據其值來決定要使用的 URI。如果 `USE_IMAGE_URI` 環境變數為 `true`，則使用 `IMAGE_URI` 環境變數作為 URI，否則使用 `CODE_URI` 環境變數作為 URI。

在部署 Lambda 函數時，您可以使用 SAM 模板文件的 `Environment` 屬性來定義這些環境變數，例如：

```yaml
Resources:
  MyFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: ./target/my-app-1.0.jar
      Handler: com.example.MyFunctionHandler
      Runtime: java8
      Environment:
        Variables:
          USE_IMAGE_URI: "true"
          CODE_URI: "./target/my-app-1.0.jar"
          IMAGE_URI: "123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:latest"
```

在此範例中，我們定義了三個環境變數：`USE_IMAGE_URI`、`CODE_URI` 和 `IMAGE_URI`。您可以在部署 Lambda 函數時更改 `USE_IMAGE_URI` 環境變數的值，以控制要使用的 URI。

## 重寫一份 `template.yml`，透過 `Environment` 屬性定義環境變數搭配 `Conditions` 以達到自動辨識、決定該以 `CodeUri` 進行本地開發測試或以 `ImageUri` 佈署到 AWS，還要加上 API Gateway 的配置

以下是一個重寫的 `template.yml`，其中使用 `Environment` 屬性定義了環境變數 `USE_IMAGE_URI`，並使用 `Conditions` 條件判斷是否要使用 `ImageUri`：

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Parameters:
  ImageUri:
    Type: String
    Default: ''

Globals:
  Function:
    Runtime: java11
    MemorySize: 512
    Timeout: 10

Resources:
  MyFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: ./target/my-app-1.0.jar
      Handler: com.example.MyFunctionHandler
      Environment:
        Variables:
          USE_IMAGE_URI: !If [UseImageUri, 'true', 'false']
          CODE_URI: ./target/my-app-1.0.jar
          IMAGE_URI: !If [UseImageUri, !Ref ImageUri, '']

  ApiGateway:
    Type: AWS::Serverless::Api
    Properties:
      StageName: prod
      DefinitionBody:
        swagger: "2.0"
        info:
          title: "My API"
        paths:
          /:
            get:
              produces:
                - "application/json"
              responses:
                "200":
                  description: "OK"
                  schema:
                    type: "object"
                    properties:
                      message:
                        type: "string"
              x-amazon-apigateway-integration:
                httpMethod: POST
                uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations"
                passthroughBehavior: "when_no_templates"
                type: "aws_proxy"
                responses:
                  default:
                    statusCode: "200"
                    responseTemplates:
                      application/json: '{ "message": "Hello, World!" }'

Conditions:
  UseImageUri: !Not [!Equals [!Ref ImageUri, '']]
```

在此範例中，我們定義了 `ImageUri` 參數作為 Lambda 函數的映像 URI，然後在 `MyFunction` 資源中定義了 `USE_IMAGE_URI`、`CODE_URI` 和 `IMAGE_URI` 三個環境變數。我們使用 `Conditions` 條件 `UseImageUri` 來判斷 `ImageUri` 參數是否被設置為空，如果不是空，則設置 `USE_IMAGE_URI` 環境變數為 `true`，並使用 `IMAGE_URI` 作為 Lambda 函數的代碼 URI；如果是空，則設置 `USE_IMAGE_URI` 環境變數為 `false`，並使用 `CODE_URI` 作為 Lambda 函數的代碼 URI。

~~在 ApiGateway 資源中，我們使用 `MyFunction` 資源的 ARN 來定義 API Gateway 的整合資源，並將其定義為 `aws_proxy` 類型，以便在 API Gateway 和 Lambda 函數之間進行整合。注意，我~~

### 2/2

以下是一份更新後的 `template.yml`，使用 `Environment` 屬性定義環境變數，搭配 `Conditions` 以達到自動辨識、決定該以 `CodeUri` 進行本地開發測試或以 `ImageUri` 佈署到 AWS，同時加上 API Gateway 的配置：

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: My Serverless Application

Globals:
  Function:
    Runtime: java11
    Timeout: 30

Resources:
  MyFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: ./target/my-app-1.0.jar
      Handler: com.example.MyFunctionHandler
      Environment:
        Variables:
          USE_IMAGE_URI:
            Fn::If:
              - UseImageUri
              - "true"
              - "false"
          CODE_URI: "./target/my-app-1.0.jar"
          IMAGE_URI:
            Fn::If:
              - UseImageUri
              - "123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:latest"
              - ""
      Policies:
        - AmazonEC2ContainerRegistryReadOnly
    Metadata:
      DockerTag:
        Fn::If:
          - UseImageUri
          - latest
          - ''
  MyApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: prod
  MyApiDeployment:
    Type: AWS::Serverless::Deployment
    Properties:
      ApiId: !Ref MyApi
  MyApiResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      ParentId: !GetAtt [MyApi, RootResourceId]
      PathPart: myresource
      RestApiId: !Ref MyApi
  MyApiMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      RestApiId: !Ref MyApi
      ResourceId: !Ref MyApiResource
      HttpMethod: GET
      Integration:
        Type: AWS_PROXY
        Uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations"
      MethodResponses:
        - StatusCode: 200
      AuthorizationType: NONE

Conditions:
  UseImageUri: !Not [!Equals [!Ref ImageUri, '']]
```

在此範例中，我們將 `USE_IMAGE_URI` 環境變數更改為一個條件，使用 `Fn::If` 函數根據 `UseImageUri` 條件的值來決定變數的值。同樣地，我們也使用 `Fn::If` 函數根據 `UseImageUri` 條件的值來決定 `IMAGE_URI` 環境變數的值，以及 `MyFunction` 函數的 `DockerTag` 屬性的值。

我們還添加了一些新的資源和屬性來配置 API Gateway。具體來說，我們添加了 `MyApi`、`MyApiDeployment`、`MyApiResource` 和 `MyApiMethod` 資源，並使用 `Integration` 屬性
