

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 使用 的容器產品的合約定價 AWS License Manager
<a name="container-license-manager-integration"></a>

對於具有合約定價的容器型產品，您可以使用 AWS License Manager 將授權與產品建立關聯。 AWS License Manager 是一種授權管理工具，可讓您的應用程式追蹤和更新客戶購買的授權 （也稱為權利）。本節提供如何將產品與 整合的相關資訊 AWS License Manager。整合完成後，您可以在其上發佈您的產品清單 AWS Marketplace。

如果您要將 License Manager 與適用於 Amazon EKS Anywhere、Amazon ECS Anywhere、Amazon Elastic Compute Cloud (Amazon EC2) 或內部部署基礎設施 AWS Marketplace 的 for Containers Anywhere 產品整合，請遵循 中的指示[將 AWS Marketplace for Containers Anywhere 與 License Manager 整合](container-anywhere-license-manager-integration.md)。

如需詳細資訊 AWS License Manager，請參閱 [AWS License Manager 使用者指南](https://docs.aws.amazon.com/license-manager/latest/userguide/license-manager.html)和 *AWS CLI 命令參考*的 [AWS License Manager](https://docs.aws.amazon.com/cli/latest/reference/license-manager/index.html)一節。

如需將 AWS License Manager 與容器產品與合約定價整合的詳細資訊，請參閱 [與賣方研討會的前期付款實驗室整合](https://catalog.workshops.aws/mpseller/en-US/container/integrate-contract)。 *AWS Marketplace *

**Topics**
+ [容器產品的合約定價](#container-contracts)
+ [授權模型](#container-LM-license-models)
+ [AWS License Manager 整合先決條件](#container-LM-prereqs)
+ [將容器產品與 License Manager 整合](#container-integrate-with-LM)
+ [License Manager API 操作](#container-LM-API-calls)
+ [授權續約和升級](#container-LM-lic-renew-upgrade)
+ [將 AWS Marketplace for Containers Anywhere 與 License Manager 整合](container-anywhere-license-manager-integration.md)

## 容器產品的合約定價
<a name="container-contracts"></a>

對於具有合約定價的容器型產品， 會根據您與客戶之間的合約，預先依您定義的付款排程向您的客戶 AWS Marketplace 收費。之後，他們有權使用這些資源。

若要設定定價，請選擇您提供給客戶的一或多個合約持續時間。您可以為每個合約期間輸入不同的價格。您的選項為 1 個月、12 個月、24 個月和 36 個月的持續時間。對於私有優惠，您可以指定自訂持續時間，以月為單位 （最多 60 個月）。

選擇最能描述您產品定價的類別。定價類別會顯示在 AWS Marketplace 網站上的客戶。您可以選擇**頻寬** (GB/s、MB/s)、**資料** (GB、MB、TB)、**主機**、**請求**、**層**或**使用者**。如果沒有任何預先定義的類別符合您的需求，您可以選擇更通用的 **Units** 類別。

優惠最多可新增 24 個維度。


**範例：資料儲存應用程式**  

|   | 1 個月價格 | 12 個月價格  | 24 個月價格  | P36-month價格  | 
| --- | --- | --- | --- | --- | 
|  未加密的資料 (GB)  |  \$11.50/GB  |  \$116.00/GB  |  \$130.00/GB  |  \$160.00/GB  | 
|  加密的資料 (GB)  |  1.55 美元/GB  |  \$116.60/GB  |  31.20 USD/GB  |  \$161.20/GB  | 


**範例：日誌監控產品**  

|   | 1 個月價格 | 12 個月價格  | 24 個月價格 | 36 個月價格 | 
| --- | --- | --- | --- | --- | 
|  基本 （監控 10 個主機、監控 5 個容器）  |  100 美元  |  1，000 美元  | 2000 美元  | 4000 美元 | 
|  標準 （監控 20 個主機、監控 10 個容器）  |  \$1200  |  2000 美元  | 4000 美元  | 8000 美元 | 
|  Pro （監控 40 個主機、監控 20 個容器）  |  400 美元  |  4000 美元  | 8000 美元  | 16，000 美元 | 
|  每小時監控的其他主機  | 10 美元  | 100 美元  |  \$1200 | 400 美元 | 
|  每小時監控的其他容器  | 10 美元  | 100 美元  |  \$1200 | 400 美元 | 

**注意**  
價格可以是下列持續時間：1 個月、12 個月、24 個月或 36 個月。您可以選擇為您的產品提供一或多個選項。每個維度的持續時間必須相同。  

**Example**  
例如，在您有 `ReadOnlyUsers`和 `AdminUsers`維度的情況下，如果您為 ReadOnlyUsers 提供每年價格，您`AdminUsers`也必須為 提供每年價格。


### 自動續約
<a name="ami-contracts-automatic-renewals"></a>

 當客戶 AWS Marketplace 使用容器合約購買您的產品時，他們可以同意自動續約合約條款。客戶每個月或 1、2 或 3 年繼續支付權利。

客戶可以隨時修改續約設定。如需詳細資訊，請參閱《 *AWS Marketplace 買方指南*》中的[修改現有合約](https://docs.aws.amazon.com/marketplace/latest/buyerguide/buyer-container-contracts.html#modify-existing-contract)。

## 授權模型
<a name="container-LM-license-models"></a>

AWS Marketplace 與 整合 AWS License Manager 支援兩種授權模型：
+ [可設定的授權模型](#container-LM-config-lic-model)
+ [分層授權模型](#container-LM-tiered-lic-model)

### 可設定的授權模型
<a name="container-LM-config-lic-model"></a>

可設定授權模型 （也稱為可量化授權模型） 會在買方取得授權後，授予買方特定數量的資源。

您可以設定定價維度和每個單價。然後，買方可以選擇他們想要購買的資源數量。

**Example 定價維度和每單位價格的**  
您可以設定定價維度 （例如資料備份） 和每單位價格 （例如 \$130/單位）。  
買方可以選擇購買 5、10 或 20 個單位。  
您的產品會追蹤和測量用量，以測量消耗的資源數量。

使用組態模型時，會以下列兩種方式之一計算權利：
+ [繪製授權](#container-floating-lic)
+ [浮動授權](#container-floating-lic) 

#### 下調授權
<a name="container-drawndown-lic"></a>

 授權是從使用時允許的授權數量集區中提取。該權利會永久簽出，且無法傳回授權集區。

**Example 處理有限數量的資料**  
使用者有權處理 500 GB 的資料。隨著他們繼續處理資料，數量會從 500 GB 的集區中抽取，直到所有 500 GB 授權都用盡為止。

對於提取授權，您可以使用 `CheckoutLicense` API 操作來查看使用的授權單位 （權限）。

**Example 的備份到 Amazon S3 的單位數/年**  
您有一個儲存產品，允許將資料備份至 Amazon Simple Storage Service 最多 1，024 個單位，為期一年。您可以使用多個 Amazon EC2 執行個體啟動您的應用程式。您的應用程式具有追蹤和彙總資料的機制。您的軟體會在每次備份時以產品 ID 呼叫 `CheckoutLicense` API 操作，或以固定間隔更新耗用的數量。  
在此範例中，您的軟體會呼叫 `CheckoutLicense` API 操作來檢查 10 個單位的資料。當總容量達到客戶購買的備份限制時，API 呼叫會失敗。

**請求**

```
linux-machine ~]$ aws license-manager checkout-license\
--product-sku "2205b290-19e6-4c76-9eea-377d6bf7la47" \
--checkout-type "PERPETUAL" \
--key-fingerprint "aws:294406891311:AWS/Marketplace:issuer-fingerprint" \
--entitlements "Name=DataConsumption, Value=l0, Unit=Count" \
--client-token "AKIAIOSFODNN7EXAMPLE"
```

**回應**

```
{"CheckoutType": "PERPETUAL",
"EntitlementsAllowed": [{
"Name": "IntermediateTier",
"Units": "None"
}],
"Expiration": "2021-04-22Tl9:02:36",
"IssuedAt": "2021-04-22Tl8:02:36",
"LicenseArn": "arn:aws:license-manager::294406891311:license:l-16bf01b...",
"LicenseConsumptionToken": "AKIAIOSFODNN7EXAMPLE"
}
```

#### 浮動授權
<a name="container-floating-lic"></a>

 授權在使用後會傳回至允許授權數量的集區。

對於浮動授權，應用程式會在使用資源時，使用 `CheckoutLicense` API 操作從權利集區簽出權利。`CheckoutLicense` API 操作的回應包含授權使用字符，這是結帳的唯一識別符。授權使用字符可用來對已簽出的權限執行其他動作，例如將它們簽入授權或延長簽出。

若要將權利檢查回集區，請在不再使用資源時使用 `CheckInLicense` API 操作。

```
aws license-manager check-in-license --license-consumption-token "f1603b3c1f574b7284db84..."
```

如果無法簽入權利 （如果應用程式當機），權利會在 60 分鐘後自動簽入集區。如果資源使用時間超過 60 分鐘，最佳實務是使用 `ExtendLicenseConsumption` API 操作，在資源使用期間，保持從集區中檢查權利。

```
aws license-manager extend-license-consumption --license-consumption-token "f1603b3c1f574b7284..."
```

**Example 來自固定上限的使用者數量**  
使用者有權在應用程式上同時存取 500 名使用者。當使用者登入和登出時，會繪製使用者並傳回 500 名使用者的集區。不過，應用程式無法從集區中提取超過 500 個使用者，因為 500 個同時使用者是固定的上限。

對於浮動權利，您可以使用 `CheckInLicense` API 操作將授權單位傳回權利集區。

**Example 一年的並行使用者數量**  
您的產品會根據並行使用者數量定價。客戶為 10 個使用者購買授權一年。客戶提供 AWS Identity and Access Management (IAM) 許可來啟動軟體。當使用者登入時，您的應用程式會呼叫 `CheckoutLicense` API 操作，將數量減少 1。當使用者登出時，應用程式會呼叫 `CheckInLicense` API 操作，將該授權傳回至集區。如果您不呼叫 `CheckInLicense`，授權單位會在 1 小時後自動簽入。

**注意**  
在下列請求中， `key-fingerprint` 不是預留位置值，而是將發佈所有授權的指紋實際值。

**請求**

```
aws license-manager checkout-license\
--product-sku "2205b290-19e6-4c76-9eea-377d6bf7la47" \
--checkout-type "PROVISIONAL" \
--key-fingerprint "aws:294406891311:AWS/Marketplace:issuer-fingerprint" \
--entitlements "Name=ReadOnlyUSers, Value=l0, Unit=Count" \
--client-token "AKIAIOSFODNN7EXAMPLE"
```

**回應**

```
{
  "CheckoutType": "PROVISIONAL",
  "EntitlementsAllowed": [
    {
      "Name": "ReadOnlyUsers", 
      "Count": 10,
      "Units": "Count",
      "Value": "Enabled"
    }
},
  "Expiration": "2021-04-22Tl9:02: 36",
  "IssuedAt": "2021-04-22Tl8:02:36",
  "LicenseArn": "arn:aws:license-manager::294406891311:license:l-16bf01b...",
  "LicenseConsumptionToken": "AKIAIOSFODNN7EXAMPLE"
}
```

### 分層授權模型
<a name="container-LM-tiered-lic-model"></a>

在買方取得授權之後，分層授權模型會授予買方特定層級或層級的應用程式功能。

您可以為您的產品建立方案，例如 Basic、Intermediate 和 Premium。然後，買方會選取其中一個預先定義的層。

應用程式不需要追蹤或計量應用程式的用量。

使用分層授權模型時，不會計算權利，而是表示客戶購買的服務層級。

如果您想要同時提供綁定功能，最好使用 層。

**Example 基本、中級和高級方案**  
客戶可以為軟體的三個可能方案之一簽署合約：基本、中級或高級。每個方案都有自己的定價。您的軟體可以透過叫用 `CheckoutLicense` API 操作並在請求中指定所有可能的層來識別客戶已註冊的層。  
請求的回應包含與客戶已取得的方案對應的權利。根據此資訊，軟體可以佈建適當的客戶體驗。

#### 請求
<a name="container-LM-tiered-request"></a>

```
linux-machine  ~]$ aws  license-manager   checkout-license\
--product-sku  "2205b290-19e6-4c76-9eea-377d6bf7la47"  \
--checkout-type  "PROVISIONAL"  \
--key-fingerprint  "aws:294406891311:AWS/Marketplace:issuer-fingerprint" \
--entitlements  "Name=BasicTier,  Unit=None"   "Name=IntermediateTier,  Unit=None"	\ "Name=PremiumTier, Unit=None"
```

#### 回應
<a name="container-LM-tiered-response"></a>

```
{
  "CheckoutType": "PROVISIONAL",
  "EntitlementsAllowed": [
    {
      "Name": "IntermediateTier", 
      "Units": "None"
    }
},
  "Expiration": "2021-04-22Tl9:02:36",
  "IssuedAt": "2021-04-22Tl8:02:36",
  "LicenseArn": "arn:aws:license-manager::294406891311:license:l-16bf01b...",
  "LicenseConsumptionToken": "AKIAIOSFODNN7EXAMPLE"
}
```

## AWS License Manager 整合先決條件
<a name="container-LM-prereqs"></a>

在發佈產品之前，您必須執行下列動作：

1. 在 中建立新的容器產品 AWS Marketplace 管理入口網站，並記下其產品代碼。

   如需詳細資訊，請參閱[概觀：建立容器產品](container-product-getting-started.md#create-container-product)。

1. 針對執行應用程式的 任務或 Pod，使用 IAM 角色搭配呼叫 `CheckoutLicense`、 `ExtendLicenseConsumption`和 `CheckInLicense` API 操作所需的 IAM 許可。

   必要的 IAM 許可詳述於下列 IAM 政策中。

------
#### [ JSON ]

****  

   ```
   {
      "Version":"2012-10-17",		 	 	 
      "Statement":[
         {
            "Sid":"VisualEditorO",
            "Effect":"Allow",
            "Action":[
               "license-manager:CheckoutLicense",
               "license-manager:GetLicense",
               "license-manager:CheckInLicense",
               "license-manager:ExtendLicenseConsumption",
               "license-manager:ListReceivedLicenses"
            ],
            "Resource":"*"
         }
      ]
   }
   ```

------

1. 使用您定義的所有定價維度的記錄對 `RegisterUsage` API 操作進行測試呼叫。

## 將容器產品與 License Manager 整合
<a name="container-integrate-with-LM"></a>

**將您的容器型產品與 License Manager 整合**

1. 設定 IAM 許可以呼叫 License Manager。如需詳細資訊，請參閱[AWS License Manager 整合先決條件](#container-LM-prereqs)。

1. 下載 AWS SDK。
**注意**  
當您的容器在 Amazon EC2 執行個體、Amazon ECS 任務或 Amazon EKS Pod 中執行時，請勿在 software. AWS credentials 中設定 AWS 登入資料，系統會在執行時間自動取得買方的登入資料。

1. 將授權檢查新增至您的產品。

   您的產品可以在應執行授權檢查的位置呼叫 `CheckoutLicense` API 操作。若要檢查授權，您的產品必須知道：

   1. 授權的信任發行者 (AWS Marketplace)

   1. 應用程式的產品 SKU （產品 ID)

   1. 檢查此應用程式的權利

   API 呼叫會根據您設定的定價授權類型而有所不同。

1. 發佈您的產品清單 AWS Marketplace。

## License Manager API 操作
<a name="container-LM-API-calls"></a>

若要管理存放在客戶 License Manager 帳戶中的授權，您的軟體可以使用下列 API 操作：
+ `GetLicense` – 軟體可查詢的 API。它會擷取已購買授權的狀態 （即已過期或即將過期），並傳送狀態通知給客戶。
+ `CheckoutLicense` – 探索使用者已購買的授權。當使用者已取用一些授權數量時，您也可以使用 `CheckoutLicense` API 操作來更新授權數量。使用 `CheckoutLicense`，您可以繼續檢查客戶使用的授權數量。當客戶耗盡所有授權時，此呼叫會傳回錯誤。如需執行 的建議節奏資訊`CheckoutLicense`，請參閱 [授權續約和升級](#container-LM-lic-renew-upgrade)。
+ `ExtendLicenseConsumption` – 在浮動維度的情況下，當軟體簽出授權時，授權會在 60 分鐘後自動返回集區。如果您想要延長授權保持簽出的時間，請使用 `ExtendLicenseConsumption` API 操作將授權再延長 60 分鐘。
+ `CheckInLicense` – 如果是浮動維度，當您想要將授權傳回權利集區時，請使用 `CheckInLicense` API 操作。
+ `ListReceivedLicenses` API – 列出買方購買的授權。

## 授權續約和升級
<a name="container-LM-lic-renew-upgrade"></a>

客戶可以在 上續約或升級其授權 AWS Marketplace 管理入口網站。進行額外的購買後， AWS Marketplace 會產生反映新權利的授權新版本。您的軟體會使用相同的 API 操作讀取新的權利。在 License Manager 整合方面，您不需要採取任何不同的動作來處理續約和升級。

由於授權續約、升級、取消等，我們建議您的產品在使用產品時定期呼叫 `CheckoutLicense` API 操作。透過定期使用 `CheckoutLicense` API 操作，產品可以偵測權利的變更，例如升級和過期。

建議您每 15 分鐘執行一次 `CheckoutLicense` API 呼叫。

# 將 AWS Marketplace for Containers Anywhere 與 License Manager 整合
<a name="container-anywhere-license-manager-integration"></a>

身為 AWS Marketplace 賣方，您可以 AWS License Manager 整合適用於 Amazon EKS Anywhere、Amazon ECS Anywhere、Amazon EC2 或內部部署基礎設施的 AWS Marketplace for Containers Anywhere 產品。以下各節提供此整合的說明。

如需與 License Manager 整合的一般資訊 AWS Marketplace，包括可用的授權模型，請參閱 [使用 的容器產品的合約定價 AWS License Manager](container-license-manager-integration.md)。如需 的詳細資訊 AWS License Manager，請參閱 [AWS License Manager 使用者指南](https://docs.aws.amazon.com/license-manager/latest/userguide/license-manager.html)和 *AWS CLI 命令參考*的 [AWS License Manager](https://docs.aws.amazon.com/cli/latest/reference/license-manager/index.html)一節。

**Topics**
+ [將 AWS Marketplace for Containers Anywhere 產品與 License Manager 整合](#containers-anywhere-integrate-with-LM)
+ [在本機測試 License Manager 整合](#container-testing-LM-integration-locally)
+ [在 Amazon EKS 上測試 License Manager 整合](#container-testing-LM-integration-EKS)
+ [使用 License Manager 浮動授權權利](#container-LM-floating-license)
+ [與現場部署的 License Manager 整合的最佳實務](#container-LM-best-practices-on-prem)
+ [`LicenseManagerCredentialsProvider` - Java 實作](#container-license-manager-cred-provider-java)
+ [`LicenseManagerCredentialsProvider` - `Golang`實作](#container-license-manager-cred-provider-golang)

## 將 AWS Marketplace for Containers Anywhere 產品與 License Manager 整合
<a name="containers-anywhere-integrate-with-LM"></a>

使用以下指示將 AWS Marketplace for Containers Anywhere 產品與 整合 AWS License Manager。

**將您的 AWS Marketplace for Containers Anywhere 產品與 License Manager 整合**

1. 開啟 Web 瀏覽器並登入 [AWS Marketplace 管理入口網站](https://aws.amazon.com/marketplace/management/)。

1. 執行下列步驟，為您的容器產品建立產品 ID。在後續步驟中，您將在容器映像中使用此 ID 進行授權檢查。

   1. 從選單列中，展開**資產**，然後選擇**容器**。

   1. 輸入產品面向客戶的名稱，然後選擇**建立**。您可以稍後變更此名稱。

   1. 記下**產品 ID**。您將在建立或更新產品定價詳細資訊時使用它。
**提示**  
如果您遺失產品 ID，您可以從**資產**功能表中 AWS Marketplace 管理入口網站 選擇**容器**，在 中找到它。**Containers** 頁面會顯示您的產品清單及其相關聯的產品 IDs。

1. 下載最新的公有 AWS SDK，然後將其安裝在您的容器應用程式中。您可以在在 [AWS 上建置的工具](https://aws.amazon.com/tools/)中找到您偏好 AWS SDK 的安裝指示。
**注意**  
若要從 Amazon EKS Anywhere 或 未提供的 Kubernetes 叢集呼叫 License Manager API 操作 AWS，您必須使用支援的 AWS SDK。若要檢視支援的 AWS SDKs 清單，請參閱[使用支援的 AWS SDK](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts-minimum-sdk.html)。

1. 使用自訂登入資料提供者建立 AWS License Manager 用戶端，以便為部署在 上的容器應用程式 AWS 以及內部部署提供登入資料。如需自訂登入資料提供者的完整原始碼`LicenseCredentialProvider`，請參閱下列章節：
   + [`LicenseManagerCredentialsProvider` - Java 實作](#container-license-manager-cred-provider-java)
   + [`LicenseManagerCredentialsProvider` - `Golang`實作](#container-license-manager-cred-provider-golang)

    `LicenseCredentialsProvider` 透過新增 來擴展 AWS SDK 的預設登入資料提供者鏈，以供內部部署使用`LicenseManagerTokenCredentialsProvider`。這透過在內部部署環境中使用 License Manager OIDC 發行的身分字符來提供憑證。您必須在應用程式 classpath `LicenseCredentialsProvider`中包含 的原始碼。
**注意**  
延伸 `DefaultCredentialsProvider`可讓相同的容器應用程式在內部部署環境中執行 AWS 和 時取得登入資料。如果容器應用程式已使用自訂登入資料提供者鏈而非預設鏈，也可以透過將 `LicenseManagerTokenCredentialsProvider`新增至自訂鏈來延伸。

   下列程式碼片段是使用 Java 建立 AWS License Manager 用戶端的範例。

   ```
   LicenseManagerClientBuilder clientBuilder = LicenseManagerClient.builder().credentialsProvider(LicenseCredentialsProvider.create());
   ```

1. 使用產品方案中每個付費容器映像的 `aws license-manager checkout-license`命令來呼叫 `CheckoutLicense` API 操作。這會檢查買方是否有權為您的應用程式使用授權。如果買方有權使用應用程式， 會`CheckoutLicense`成功並傳回請求的權限及其值。如果買方無權使用應用程式， 會`CheckoutLicense`擲回例外狀況。

   呼叫 `CheckoutLicense` API 操作時需要下列參數：
   + `CheckoutType` – 有效值為 `PROVISIONAL`或 `PERPETUAL`：
     + 當已從集區中耗盡已簽出的權限數量`PERPETUAL`時，請使用 。

       範例：買方有權處理 500 GB 的資料。隨著他們繼續處理資料，數量會從 500 GB 的集區中消耗。
     + `PROVISIONAL` 用於浮動授權權利，其中權利會從集區中簽出，並在使用後傳回。

       範例：使用者有權在應用程式上同時存取 500 名使用者。當使用者登入或登出時，系統會將使用者繪製或傳回至 500 名使用者的集區。若要進一步了解浮動授權權利，請參閱 [使用 License Manager 浮動授權權利](#container-LM-floating-license)。
   + `ClientToken` – 唯一且區分大小寫的識別符。我們建議針對每個唯一請求使用隨機 UUID。
   + `Entitlements` – 要簽出的權限清單。
     + 針對功能權利，提供 `Name`和 `Unit` 屬性，如下所示。

       ```
       {
         "Name": "<Entitlement_Name>",
         "Unit": "None"
       }
       ```
     + 針對計數權利，提供 `Name`、 `Unit`和 `Count` 屬性，如下所示。

       ```
       {
         "Name": "<Entitlement_Name>",
         "Unit": "<Entitlement_Unit>",
         "Value": <Desired_Count>
       }
       ```
   + `KeyFingerprint` – 所發行授權的金鑰指紋 AWS Marketplace 為 `aws:294406891311:AWS/Marketplace:issuer-fingerprint`。使用此金鑰指紋可確保授權是由 發出 AWS Marketplace ，而不是由不可靠的實體發出。
   + `ProductSKU` – 上一個步驟 AWS Marketplace 管理入口網站 中在 上產生的產品 ID。

   下列程式碼片段是使用 進行 `CheckoutLicense` API 操作的呼叫範例 AWS CLI。

   ```
   aws license-manager checkout-license \
   --product-sku "2205b290-19e6-4c76-9eea-377d6bf71a47" \
   --checkout-type "PROVISIONAL" \
   --client-token "79464194dca9429698cc774587a603a1" \
   --entitlements "Name=AWS::Marketplace::Usage/Drawdown/DataConsumption, Value=10, Unit=Gigabytes" \
   --key-fingerprint "aws:294406891311:AWS/Marketplace:issuer-fingerprint"
   ```
**注意**  
若要檢查授權，容器應用程式需要傳出網路存取權才能使用 License Manager。部署在內部部署的應用程式可能會遇到不可靠或緩慢的傳出網路存取。呼叫 License Manager 時，這些應用程式應包含足夠的重試次數。如需詳細資訊，請參閱[與現場部署的 License Manager 整合的最佳實務](#container-LM-best-practices-on-prem)。

1. 定期呼叫 `CheckoutLicense` API 操作，以識別因續約、升級或取消 而導致客戶授權的任何變更 AWS Marketplace。節奏取決於應用程式。我們建議您每天檢查一次授權，以自動收取變更，而無需任何買方介入。

   部署於內部部署的應用程式可能具有不可靠的傳出網路存取權，以定期檢查授權。在這種情況下，應用程式應使用快取的授權以獲得足夠的彈性。如需詳細資訊，請參閱[與現場部署的 License Manager 整合的最佳實務](#container-LM-best-practices-on-prem)。

1. 將`CheckoutLicense`呼叫與容器應用程式整合之後，請建置具有變更的 Docker 容器映像新版本。

1. 更新應用程式的 Helm Chart 以接受 Kubernetes 秘密作為選用輸入，其中包含使用 License Manager APIs組態。組態秘密將包含 License Manager 發行的身分字符和 角色，該 AWS Identity and Access Management 角色將由先前描述的自訂登入資料提供者使用，以取得在現場部署容器應用程式時呼叫 License Manager APIs 的 AWS 登入資料。此外，將 新增 AWS 區域 為預設值為 的輸入`us-east-1`。

   現場部署容器應用程式的買方可以透過容器產品的 AWS Marketplace 買方體驗來建立 Kubernetes 秘密。提供 Kubernetes 秘密名稱做為`helm install`命令的輸入。組態秘密的設定格式如下。

   ```
   apiVersion: v1
   kind: Secret
   metadata:
     name: aws-marketplace-license-config
   type: Opaque
   stringData:
     license_token: <token_value> // License Manager issued JWT token
     iam_role: <role_arn> // AWS Identity and Access Management role to assume with license token
   ```

1. 針對與 整合的容器映像，更新 Helm Chart 中的應用程式部署範本 AWS License Manager ，以包含下列項目：
   + Pod 的服務帳戶 – 在 Amazon EKS 上的 Helm 部署需要 服務帳戶。它用於透過在容器映像上設定服務帳戶的 IAM 角色來取得呼叫 License Manager API 操作的許可。如需服務帳戶的 IAM 角色詳細資訊，請參閱[服務帳戶的 IAM 角色](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html)。
   + 內部部署的授權存取 – 需要授權組態秘密，才能提供登入資料和適當的許可，以在內部部署環境中呼叫 Helm 部署的 License Manager API 操作。買方將從 AWS Marketplace 買方體驗產生授權秘密並提供給 Helm。

   下列程式碼片段是具有服務帳戶、授權組態和映像提取秘密的範例部署規格。

   ```
   apiVersion: apps/v1
   kind: Deployment
   metadata:
     name: example-app
   spec:
     replicas: 1
     selector:
       matchLabels:
         app: example-app
     template:
       metadata:
         labels:
           app: example-app
   spec:
         // Service account for pod
         serviceAccountName: {{ .Values.serviceAccountName }}
         containers:
           - name: example-app
             image: example-app
             ports:
               - containerPort: 8001
   // Add the following conditional attributes
   {{ - if .Values.awsmp.licenseConfigSecretName }}
             //Mount the license volume to the container image
             volumeMounts:
               - name: awsmp-product-license
                 mountPath: "/var/run/secrets/product-license"
             //Add following environment variable to container for credential
   provider
             env:
               - name: AWS_WEB_IDENTITY_REFRESH_TOKEN_FILE
                 value: "/var/run/secrets/product-license/license_token"
               - name: AWS_ROLE_ARN
                   valueFrom:
                       secretKeyRef:
                       name: {{ .Values.aws.licenseConfigSecretName }}
                       key: iam_role
         //Mount the license secret as a volume to the pod
         volumes:
           - name: awsmp-product-license
             secret:
               secretName: {{ .Values.aws.licenseConfigSecretName }}
               optional: true
   {{ - end }}
   ```
**注意**  
授權組態秘密是選用的。買方只會將 值用於內部部署。對於 AWS 部署，部署規格必須包含 License Manager 整合映像的服務帳戶。

1. 透過執行以下章節中的步驟，在本機和 Amazon EKS 上測試 License Manager 整合：

   1. [在本機測試 License Manager 整合](#container-testing-LM-integration-locally)

   1. [在 Amazon EKS 上測試 License Manager 整合](#container-testing-LM-integration-EKS)

1. 在現場部署 AWS 和現場部署成功驗證 License Manager 整合之後，您可以依照中的步驟建立容器產品清單[概觀：建立容器產品](container-product-getting-started.md#create-container-product)。

## 在本機測試 License Manager 整合
<a name="container-testing-LM-integration-locally"></a>

您可以使用 minikube 或任何其他設定來測試本機任何 Kubernetes 叢集上的 License Manager 整合。確定 Kubernetes 叢集具有傳出網際網路存取權，以呼叫 License Manager API 操作。

**在本機測試 License Manager 整合**

1. 在具有所需權限的測試賣方帳戶中建立測試授權。若要設定測試授權，請參閱 *AWS License Manager API 參考*中的 [CreateLicense](https://docs.aws.amazon.com/license-manager/latest/APIReference/API_CreateLicense.html)。或者，使用以下指令碼來建立測試授權，然後建立授權給測試買方帳戶以取用授權。下列指令碼使用測試賣方帳戶登入資料。

   ```
   read -p 'AWS Account for test buyer: ' TEST_BUYER_ACCOUNT_ID
   read -p 'License entitlements: ' ENTITLEMENTS
   
   # TEST_SELLER_ACCOUNT_ID="109876543210"
   # ENTITLEMENTS="{\"Name\": \"ByData\",\"MaxCount\": 1000,\"Overage\":true,\"Unit\": \"Gigabits\",\"AllowCheckIn\": true}"
   
   # Create License
   
   NOW=$(date +"%Y-%m-%dT00:00:00+00:00")
   
   PRODUCT_NAME="My awesome product"
   PRODUCT_SKU="c97b7825-44c4-4f42-b025-12baa4c171e0"
   
   LICENSE_BENEFICIARY=" arn:aws:iam::$TEST_BUYER_ACCOUNT_ID:root "
   LICENSE_ISSUER_NAME="test-seller"
   LICENSE_NAME="test-seller-license"
   
   CLIENT_TOKEN="b3920968-a94f-4547-af07-3dd232319367"
   CONSUMPTION_TTL=180
   CONSUMPTION_RENEW_TYPE="None"
   
   HOME_REGION="us-east-1"
   
   LICENSE_ARN=$(aws license-manager create-license --license-name "$LICENSE_NAME" --product-name "$PRODUCT_NAME" --product-sku "$PRODUCT_SKU" --issuer Name="$LICENSE_ISSUER_NAME" --home-region "$HOME_REGION" --validity Begin="$NOW" --entitlements "$ENTITLEMENTS" --beneficiary "$LICENSE_BENEFICIARY" --consumption-configuration RenewType="$CONSUMPTION_RENEW_TYPE",ProvisionalConfiguration={MaxTimeToLiveInMinutes=$CONSUMPTION_TTL} --client-token "$CLIENT_TOKEN" | jq -r ".LicenseArn" )
   
   echo "License arn: $LICENSE_ARN"
   
   # Create Grant
   
   GRANT_TOKEN="e9a14140-4fca-4219-8230-57511a6ea6"
   GRANT_NAME="test-grant"
   
   GRANT_ARN=$(aws license-manager create-grant --grant-name "$GRANT_NAME" --license-arn "$LICENSE_ARN" --principals "$LICENSE_BENEFICIARY" --home-region "$HOME_REGION" --client-token "$GRANT_TOKEN" --allowed-operations "CheckoutLicense" "CheckInLicense" "ExtendConsumptionLicense" "CreateToken" | jq -r ".GrantArn")
   
   echo "Grant arn: $GRANT_ARN"
   ```

1. 使用先前定義的秘密格式，使用授權字符和 IAM 角色建立 Kubernetes 秘密。使用 License Manager `CreateToken` API 操作來產生授權字符。然後，使用 IAM `CreateRole` API 操作來建立具有許可和信任政策的 IAM 角色。請參閱下列指令碼中的範例。下列指令碼使用測試買方帳戶登入資料。

   ```
   read -p 'AWS Account for test license: ' TEST_ACCOUNT_ID
   read -p 'License Arn' LICENSE_ARN
   # Create IAM Role
   ROLE_NAME="AWSLicenseManagerConsumptionTestRole"
   ROLE_DESCRIPTION="Role to test AWS License Manager integration on-prem"
   ROLE_POLICY_ARN="arn:aws:iam::aws:policy/service-role/AWSLicenseManagerConsumptionPolicy"
   ROLE_TRUST_POLICY="{\"Version\": \"2012-10-17\",\"Statement\": [{ \"Effect\":\"Allow\", \"Principal\": { \"Federated\": \"openid-license-manager.amazonaws.com\" }, \"Action\": \"sts:AssumeRoleWithWebIdentity\",\"Condition\": { \"ForAnyValue:StringLike\": { \"openid-license-manager.amazonaws.com:amr\": \"aws:license-manager:token-issuer-account-id:${TEST_ACCOUNT_ID}\" }}}]}"
   ROLE_SESSION_DURATION=3600
   
   ROLE_ARN=$(aws iam create-role --role-name "$ROLE_NAME" --description "$ROLE_DESCRIPTION" --assume-role-policy-document "$ROLE_TRUST_POLICY" --max-session-duration $ROLE_SESSION_DURATION | jq ".Role" | jq -r ".Arn")
   
   aws iam attach-role-policy --role-name "$ROLE_NAME" --policy-arn "$ROLE_POLICY_ARN"
   
   echo "Role arn: $ROLE_ARN"
   
   # Create Token
   CLIENT_TOKEN="b3920968-a94f-4547-af07-3dd232319367"
   
   TOKEN=$(aws license-manager create-token --license-arn $LICENSE_ARN --role-arns $ROLE_ARN --client-token $CLIENT_TOKEN | jq '.Token')
   
   echo "License access token: $TOKEN"c
   ```

1. 設定在外部託管的任何 Kubernetes 叢集 AWS。使用它來測試容器應用程式是否可以從 以外的環境連線至 AWS License Manager API， AWS 並且自訂登入資料提供者已與應用程式充分整合。

1. 將先前產生的授權字符和 IAM 角色部署到本機 Kubernetes 叢集。

   ```
   kubectl create secret generic "awsmp-license-access-config" \
   --from-literal=license_token=${TOKEN} \
   --from-literal=iam_role=${ROLE_ARN}
   ```

1. 透過 Helm 使用秘密名稱做為輸入來部署應用程式，並確認應用程式可以呼叫 License Manager API 操作來執行權利檢查。如需 Helm 和部署規格變更，請參閱 中的步驟 9[將 AWS Marketplace for Containers Anywhere 產品與 License Manager 整合](#containers-anywhere-integrate-with-LM)。

## 在 Amazon EKS 上測試 License Manager 整合
<a name="container-testing-LM-integration-EKS"></a>

您也可以在 Amazon EKS 上測試 License Manager 整合。測試以確保應用程式可以在沒有授權組態秘密的情況下呼叫 License Manager API 操作。此外，請確定服務帳戶可用來設定服務帳戶的 IAM 角色 (IRSA)，並提供應用程式相關登入資料。

**在 Amazon EKS 上測試 License Manager 整合**

1. 在具有所需權利的測試賣方帳戶中建立測試授權。請參閱 [CreateLicense API 參考](https://docs.aws.amazon.com/license-manager/latest/APIReference/API_CreateLicense.html)以設定您的測試授權，或使用下列指令碼建立一個授權，並建立授權給測試買方帳戶以取用授權。下列指令碼使用測試賣方帳戶登入資料。

   ```
   read -p 'AWS Account for test buyer: ' TEST_BUYER_ACCOUNT_ID
   read -p 'License entitlements: ' ENTITLEMENTS
   
   # TEST_SELLER_ACCOUNT_ID="109876543210"
   # ENTITLEMENTS="{\"Name\": \"ByData\",\"MaxCount\": 1000,\"Overage\": true,\"Unit\": \"Gigabits\",\"AllowCheckIn\": true}"
   
   # Create License
   
   NOW=$(date +"%Y-%m-%dT00:00:00+00:00")
   
   PRODUCT_NAME="My awesome product"
   PRODUCT_SKU="c97b7825-44c4-4f42-b025-12baa4c171e0"
   
   LICENSE_BENEFICIARY=" arn:aws:iam::$TEST_BUYER_ACCOUNT_ID:root "
   LICENSE_ISSUER_NAME="test-seller"
   LICENSE_NAME="test-seller-license"
   
   CLIENT_TOKEN="b3920968-a94f-4547-af07-3dd232319367"
   CONSUMPTION_TTL=180
   CONSUMPTION_RENEW_TYPE="None"
   
   HOME_REGION="us-east-1"
   
   LICENSE_ARN=$(aws license-manager create-license --license-name "$LICENSE_NAME" --product-name "$PRODUCT_NAME" --product-sku "$PRODUCT_SKU" --issuer Name="$LICENSE_ISSUER_NAME" --home-region "$HOME_REGION" --validity Begin="$NOW" --entitlements "$ENTITLEMENTS" --beneficiary "$LICENSE_BENEFICIARY" --consumption-configuration RenewType="$CONSUMPTION_RENEW_TYPE",ProvisionalConfiguration={MaxTimeToLiveInMinutes=$CONSUMPTION_TTL} --client-token "$CLIENT_TOKEN" | jq -r ".LicenseArn" )
   
   echo "License arn: $LICENSE_ARN"
   
   # Create Grant
   
   GRANT_TOKEN="e9a14140-4fca-4219-8230-57511a6ea6"
   GRANT_NAME="test-grant"
   
   GRANT_ARN=$(aws license-manager create-grant --grant-name "$GRANT_NAME" --license-arn "$LICENSE_ARN" --principals "$LICENSE_BENEFICIARY" --home-region "$HOME_REGION" --client-token "$GRANT_TOKEN" --allowed-operations "CheckoutLicense" "CheckInLicense" "ExtendConsumptionLicense" "CreateToken" | jq -r ".GrantArn")
   
   echo "Grant arn: $GRANT_ARN"
   ```

1. 建立所需組態的測試 Amazon EKS 叢集，或執行下列命令以使用預設組態。

   ```
   aws ec2 create-key-pair --region us-west-2 --key-name eks-key-pair
   ```

   ```
   eksctl create cluster \
   --name awsmp-eks-test-example \
   --region us-west-2 \
   --with-oidc \
   --ssh-access \
   --ssh-public-key eks-key-pair
   ```

1. 為現有叢集建立服務帳戶，並將其與 IAM 角色建立關聯。下列命令會使用 建立 IAM 角色`AWSLicenseManagerConsumptionPolicy`。然後，命令會將它連接到 Amazon EKS 叢集`test_sa`的服務帳戶，其中應部署 License Manager 整合映像。因此，服務帳戶可以取得適當的登入資料來呼叫 License Manager API 操作。

   ```
   eksctl create iamserviceaccount \
   --name test_sa \
   --namespace test_namespace \
   --cluster awsmp-eks-test-example \
   --attach-policy-arn "arn:aws:iam::aws:policy/service-role/AWSLicenseManagerConsumptionPolicy" \
   --approve \
   --override-existing-serviceaccounts
   ```

1. 在 IAM 角色與上一個命令相關聯的服務帳戶中，透過 Helm 部署應用程式。確認應用程式可以呼叫 License Manager API 操作來執行權利檢查。

## 使用 License Manager 浮動授權權利
<a name="container-LM-floating-license"></a>

使用浮動授權時，當使用者登入應用程式時，會從可用授權集區中提取授權。當使用者登出時，授權會新增回可用的授權集區。

對於浮動授權，應用程式會使用 `CheckoutLicense` API 操作，在使用資源時從權利集區簽出權利。`CheckoutLicense` API 操作的回應包含授權使用字符，這是結帳的唯一識別符。授權使用字符可以對已簽出的權限執行其他動作，例如將它們簽回授權集區或延長簽出。

當資源不再使用時，應用程式會使用 `CheckInLicense` API 操作將權利檢查回集區。

```
aws license-manager check-in-license \
--license-consumption-token "f1603b3c1f574b7284db84a9e771ee12"
```

如果將授權檢查回集區失敗，例如，如果應用程式在操作期間當機，則會在 60 分鐘後自動將權利檢查回集區。因此，如果資源的使用時間超過 60 分鐘，最佳實務是將權利保留在集區之外。若要這樣做，只要資源正在使用，請使用 `ExtendLicenseConsumption` API 操作。

```
aws license-manager extend-license-consumption \
--license-consumption-token "f1603b3c1f574b7284db84a9e771ee12"
```

## 與現場部署的 License Manager 整合的最佳實務
<a name="container-LM-best-practices-on-prem"></a>

內部部署環境中的容器應用程式部署可能會遇到不可靠的傳出網路存取。使用下列最佳實務來新增彈性，以避免因網際網路連線能力不佳造成潛在問題而導致買方的服務中斷：
+ **充分重試** – 暫時性網路問題可能會讓您的應用程式無法連線到 AWS License Manager。實作重試最多 30 分鐘，並呈指數退避。這有助於避免短期中斷或網路問題。
+ **避免硬性限制** – 部署在連線叢集中的應用程式可以定期檢查授權，以識別因升級或續約而產生的任何變更。透過不可靠的傳出存取，應用程式可能無法識別這些變更。盡可能避免由於無法透過 License Manager 檢查授權而導致買方的服務中斷。當授權過期且無法檢查授權是否有效時，應用程式可以恢復免費試用或開放原始碼的體驗。
+ **通知客戶** – 使用快取的授權時，授權的任何變更 （包括續約或升級） 都不會自動反映在執行中的工作負載上。通知您的客戶 （他們必須暫時允許傳出存取應用程式，以便應用程式可以更新其快取的授權。例如，透過應用程式本身或文件通知客戶。同樣地，當回復到一組較低的功能時，請通知客戶其權利已耗盡或授權已過期。然後，他們可以選擇升級或續約。

## `LicenseManagerCredentialsProvider` - Java 實作
<a name="container-license-manager-cred-provider-java"></a>

`LicenseCredentialsProvider` 透過新增 來擴展 AWS SDK 的預設登入資料提供者鏈，以供內部部署使用`LicenseManagerTokenCredentialsProvider`。

**`LicenseCredentialsProvider`**

```
package com.amazon.awsmp.license;

import software.amazon.awssdk.auth.credentials.AwsCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProviderChain;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.auth.credentials.internal.LazyAwsCredentialsProvider;
import software.amazon.awssdk.utils.SdkAutoCloseable;

public class LicenseCredentialsProvider implements AwsCredentialsProvider, SdkAutoCloseable {
    private static final LicenseCredentialsProvider CREDENTIALS_PROVIDER = new LicenseCredentialsProvider();
    private final LazyAwsCredentialsProvider providerChain;

    private LicenseCredentialsProvider() {
        this.providerChain = createChain();
    }

    public static LicenseCredentialsProvider create() {
        return CREDENTIALS_PROVIDER;
    }

    @Override
    public AwsCredentials resolveCredentials() {
        return this.providerChain.resolveCredentials();
    }

    @Override
    public void close() {
        this.providerChain.close();
    }

    private LazyAwsCredentialsProvider createChain() {
        return LazyAwsCredentialsProvider.create(() -> {
            AwsCredentialsProvider[] credentialsProviders = new AwsCredentialsProvider[]{
                    DefaultCredentialsProvider.create(),
                    LicenseManagerTokenCredentialsProvider.create()};

            return AwsCredentialsProviderChain.builder().reuseLastProviderEnabled(true)
                    .credentialsProviders(credentialsProviders).build();
        });
    }
}
```

**`LicenseManagerTokenCredentialsProvider`**

`LicenseManagerTokenCredentialsProvider` 在內部部署環境中使用 License Manager OIDC 發行的身分字符來提供登入資料。您必須在應用程式 classpath `LicenseCredentialsProvider`中包含 的原始碼。

```
package com.amazon.awsmp.license;

import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
import software.amazon.awssdk.auth.credentials.AwsCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.core.SdkSystemSetting;
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
import software.amazon.awssdk.core.retry.RetryPolicyContext;
import software.amazon.awssdk.core.retry.conditions.OrRetryCondition;
import software.amazon.awssdk.core.retry.conditions.RetryCondition;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain;
import software.amazon.awssdk.services.licensemanager.LicenseManagerClient;
import software.amazon.awssdk.services.licensemanager.model.GetAccessTokenRequest;
import software.amazon.awssdk.services.licensemanager.model.GetAccessTokenResponse;
import software.amazon.awssdk.services.sts.StsClient;
import software.amazon.awssdk.services.sts.auth.StsAssumeRoleWithWebIdentityCredentialsProvider;
import software.amazon.awssdk.services.sts.model.AssumeRoleWithWebIdentityRequest;
import software.amazon.awssdk.services.sts.model.IdpCommunicationErrorException;
import software.amazon.awssdk.utils.IoUtils;
import software.amazon.awssdk.utils.SdkAutoCloseable;
import software.amazon.awssdk.utils.StringUtils;
import software.amazon.awssdk.utils.SystemSetting;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.function.Supplier;

public class LicenseManagerTokenCredentialsProvider implements AwsCredentialsProvider, SdkAutoCloseable {

    private final StsAssumeRoleWithWebIdentityCredentialsProvider credentialsProvider;
    private final RuntimeException loadException;

    private Path licenseAccessTokenFile;
    private String roleArn;
    private String roleSessionName;
    private StsClient stsClient;
    private LicenseManagerClient lmClient;

    public static LicenseManagerTokenCredentialsProvider create() {
        return new Builder().build();
    }

    @Override
    public AwsCredentials resolveCredentials() {
        if (this.loadException != null) {
            throw this.loadException;
        }
        return this.credentialsProvider.resolveCredentials();
    }

    @Override
    public void close() {
        IoUtils.closeQuietly(this.credentialsProvider, null);
        IoUtils.closeQuietly(this.stsClient, null);
        IoUtils.closeIfCloseable(this.lmClient, null);
    }

    private LicenseManagerTokenCredentialsProvider(Builder builder) {
        StsAssumeRoleWithWebIdentityCredentialsProvider credentialsProvider = null;
        RuntimeException loadException = null;

        try {
            this.licenseAccessTokenFile = Paths.get(StringUtils.trim(LicenseSystemSetting.AWS_WEB_IDENTITY_REFRESH_TOKEN_FILE.getStringValueOrThrow()));
            this.roleArn = SdkSystemSetting.AWS_ROLE_ARN.getStringValueOrThrow();
            this.roleSessionName = SdkSystemSetting.AWS_ROLE_SESSION_NAME.getStringValue().orElse("aws-sdk-java-" + System.currentTimeMillis());
            this.stsClient = builder.stsClient != null ? builder.stsClient : StsClientFactory.create();
            this.lmClient = builder.lmClient != null ? builder.lmClient : LicenseManagerClientFactory.create();

            AssumeRoleWithWebIdentityRequest request = AssumeRoleWithWebIdentityRequest.builder()
                    .roleArn(this.roleArn).roleSessionName(this.roleSessionName).build();

            Supplier<AssumeRoleWithWebIdentityRequest> supplier = new AssumeRoleRequestSupplier(request,
                    this.licenseAccessTokenFile, this.lmClient);

            credentialsProvider = StsAssumeRoleWithWebIdentityCredentialsProvider.builder()
                    .stsClient(this.stsClient).refreshRequest(supplier).build();
        } catch (RuntimeException ex) {
            loadException = ex;
        }

        this.credentialsProvider = credentialsProvider;
        this.loadException = loadException;
    }

    public static final class Builder {
        private Path licenseAccessTokenFile;
        private String roleArn;
        private String roleSessionName;
        private StsClient stsClient;
        private LicenseManagerClient lmClient;

        public LicenseManagerTokenCredentialsProvider build() {
            return new LicenseManagerTokenCredentialsProvider(this);
        }

        public LicenseManagerTokenCredentialsProvider.Builder licenseAccessTokenFile(Path licenseAccessTokenFile) {
            this.licenseAccessTokenFile = licenseAccessTokenFile;
            return this;
        }

        public LicenseManagerTokenCredentialsProvider.Builder roleArn(String roleArn) {
            this.roleArn = roleArn;
            return this;
        }

        public LicenseManagerTokenCredentialsProvider.Builder roleSessionName(String roleSessionName) {
            this.roleSessionName = roleSessionName;
            return this;
        }

        public LicenseManagerTokenCredentialsProvider.Builder stsClient(StsClient stsClient) {
            this.stsClient = stsClient;
            return this;
        }

        public LicenseManagerTokenCredentialsProvider.Builder lmClient(LicenseManagerClient lmClient) {
            this.lmClient = lmClient;
            return this;
        }
    }

    private static final class AssumeRoleRequestSupplier implements Supplier {
        private final LicenseManagerClient lmClient;
        private final AssumeRoleWithWebIdentityRequest request;
        private final Path webIdentityRefreshTokenFile;

        AssumeRoleRequestSupplier(final AssumeRoleWithWebIdentityRequest request,
                                                 final Path webIdentityRefreshTokenFile,
                                                 final LicenseManagerClient lmClient) {
            this.lmClient = lmClient;
            this.request = request;
            this.webIdentityRefreshTokenFile = webIdentityRefreshTokenFile;
        }

        public AssumeRoleWithWebIdentityRequest get() {
            return this.request.toBuilder()
                    .webIdentityToken(getIdentityToken())
                    .build();
        }

        private String getIdentityToken() {
            return refreshIdToken(readRefreshToken(this.webIdentityRefreshTokenFile));
        }

        private String readRefreshToken(Path file) {
            try (InputStream webIdentityRefreshTokenStream = Files.newInputStream(file)) {
                return IoUtils.toUtf8String(webIdentityRefreshTokenStream);
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
        }

        private String refreshIdToken(String licenseRefreshToken) {
            final GetAccessTokenRequest request = GetAccessTokenRequest.builder()
                    .token(licenseRefreshToken)
                    .build();

            GetAccessTokenResponse response = this.lmClient.getAccessToken(request);
            return response.accessToken();
        }
    }

    private static final class LicenseManagerClientFactory {
        private static final Duration DEFAULT_API_TIMEOUT = Duration.ofSeconds(30);
        private static final Duration DEFAULT_API_ATTEMPT_TIMEOUT = Duration.ofSeconds(10);

        public static LicenseManagerClient create() {
            return getLicenseManagerClient();
        }

        private static LicenseManagerClient getLicenseManagerClient() {
            ClientOverrideConfiguration configuration = ClientOverrideConfiguration.builder()
                    .apiCallTimeout(DEFAULT_API_TIMEOUT)
                    .apiCallAttemptTimeout(DEFAULT_API_ATTEMPT_TIMEOUT)
                    .build();

            LicenseManagerClient client = LicenseManagerClient.builder()
                    .region(configureLicenseManagerRegion())
                    .credentialsProvider(AnonymousCredentialsProvider.create())
                    .overrideConfiguration(configuration).build();
            return client;
        }

        private static Region configureLicenseManagerRegion() {
            Region defaultRegion = Region.US_EAST_1;

            Region region;
            try {
                region = (new DefaultAwsRegionProviderChain()).getRegion();
            } catch (RuntimeException ex) {
                region = defaultRegion;
            }
            return region;
        }
    }

    private static final class StsClientFactory {
        private static final Duration DEFAULT_API_TIMEOUT = Duration.ofSeconds(30);
        private static final Duration DEFAULT_API_ATTEMPT_TIMEOUT = Duration.ofSeconds(10);

        public static StsClient create() {
            return getStsClient();
        }

        private static StsClient getStsClient() {
            OrRetryCondition retryCondition = OrRetryCondition.create(new StsRetryCondition(),
                    RetryCondition.defaultRetryCondition());

            ClientOverrideConfiguration configuration = ClientOverrideConfiguration.builder()
                    .apiCallTimeout(DEFAULT_API_TIMEOUT)
                    .apiCallAttemptTimeout(DEFAULT_API_ATTEMPT_TIMEOUT)
                    .retryPolicy(r -> r.retryCondition(retryCondition))
                    .build();

            return StsClient.builder()
                    .region(configureStsRegion())
                    .credentialsProvider(AnonymousCredentialsProvider.create())
                    .overrideConfiguration(configuration).build();
        }

        private static Region configureStsRegion() {
            Region defaultRegion = Region.US_EAST_1;
            Region stsRegion;
            try {
                stsRegion = (new DefaultAwsRegionProviderChain()).getRegion();
            } catch (RuntimeException ex) {
                stsRegion = defaultRegion;
            }
            return stsRegion;
        }

        private static final class StsRetryCondition implements RetryCondition {
            public boolean shouldRetry(RetryPolicyContext context) {
                return context.exception() instanceof IdpCommunicationErrorException;
            }
        }
    }

    private enum LicenseSystemSetting implements SystemSetting {
        AWS_WEB_IDENTITY_REFRESH_TOKEN_FILE("aws.webIdentityRefreshTokenFile");

        private String systemProperty;
        private String defaultValue = null;

        LicenseSystemSetting(String systemProperty) {
            this.systemProperty = systemProperty;
        }

        @Override
        public String property() {
            return this.systemProperty;
        }

        @Override
        public String environmentVariable() {
            return this.name();
        }

        @Override
        public String defaultValue() {
            return this.defaultValue;
        }
    }
}
```

## `LicenseManagerCredentialsProvider` - `Golang`實作
<a name="container-license-manager-cred-provider-golang"></a>

**`LicenseCredentialsProvider`**

`LicenseCredentialsProvider` 透過新增 來擴展 AWS SDK 的預設登入資料提供者鏈，以供內部部署使用`LicenseManagerTokenCredentialsProvider`。

```
package lib

import (
	"context"
	"fmt"
	"sync"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
)

// LicenseCredentialsProvider is the custom credential provider that can retrieve valid temporary aws credentials
type LicenseCredentialsProvider struct {
	fallBackProvider   aws.CredentialsProvider
	mux                sync.RWMutex
	licenseCredentials aws.Credentials
	err                error
}

// NewLicenseCredentialsProvider method will create a LicenseCredentialProvider Object which contains valid temporary aws credentials
func NewLicenseCredentialsProvider() (*LicenseCredentialsProvider, error) {
	licenseCredentialProvider := &LicenseCredentialsProvider{}
	fallBackProvider, err := createCredentialProvider()
	if err != nil {
		return licenseCredentialProvider, fmt.Errorf("failed to create LicenseCredentialsProvider, %w", err)
	}
	licenseCredentialProvider.fallBackProvider = fallBackProvider
	return licenseCredentialProvider, nil
}

// Retrieve method will retrieve temporary aws credentials from the credential provider
func (l *LicenseCredentialsProvider) Retrieve(ctx context.Context) (aws.Credentials, error) {
	l.mux.RLock()
	defer l.mux.RUnlock()
	l.licenseCredentials, l.err = l.fallBackProvider.Retrieve(ctx)
	return l.licenseCredentials, l.err
}

func createCredentialProvider() (aws.CredentialsProvider, error) {
	// LoadDefaultConfig will examine all "default" credential providers
	ctx := context.TODO()
	cfg, err := config.LoadDefaultConfig(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to create FallBackProvider, %w", err)
	}

	var useFallbackProvider bool
	if cfg.Credentials != nil {
		if _, err := cfg.Credentials.Retrieve(ctx); err != nil {
			// If the "default" credentials provider cannot retrieve credentials, enable fallback to customCredentialsProvider.
			useFallbackProvider = true
		}
	} else {
		useFallbackProvider = true
	}

	if useFallbackProvider {
		customProvider, err := newLicenseManagerTokenCredentialsProvider()
		if err != nil {
			return cfg.Credentials, fmt.Errorf("failed to create fallBackProvider, %w", err)
		}
		// wrap up customProvider with CredentialsCache to enable caching
		cfg.Credentials = aws.NewCredentialsCache(customProvider)
	}
	return cfg.Credentials, nil
}
```

**`LicenseManagerTokenCredentialsProvider`**

`LicenseManagerTokenCredentialsProvider` 在內部部署環境中使用 License Manager OIDC 發行的身分字符來提供登入資料。您必須在應用程式 classpath `LicenseCredentialsProvider`中包含 的原始碼。

```
package lib

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"sync"
	"time"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/sts"
)

const awsRefreshTokenFilePathEnvVar = "AWS_LICENSE_ACCESS_FILE"

// licenseManagerTokenCredentialsProvider defines and contains StsAssumeRoleWithWebIdentityProvider
type licenseManagerTokenCredentialsProvider struct {
	stsCredentialProvider *stsAssumeRoleWithWebIdentityProvider
	mux                   sync.RWMutex
	licenseCredentials    aws.Credentials
	err                   error
}

// Retrieve method will retrieve credentials from credential provider.
// Make this method public to make this provider satisfies CredentialProvider interface
func (a *licenseManagerTokenCredentialsProvider) Retrieve(ctx context.Context) (aws.Credentials, error) {
	a.mux.RLock()
	defer a.mux.RUnlock()
	a.licenseCredentials, a.err = a.stsCredentialProvider.Retrieve(ctx)
	return a.licenseCredentials, a.err
}

// newLicenseManagerTokenCredentialsProvider will create and return a LicenseManagerTokenCredentialsProvider Object which wraps up stsAssumeRoleWithWebIdentityProvider
func newLicenseManagerTokenCredentialsProvider() (*licenseManagerTokenCredentialsProvider, error) {
	// 1. Retrieve variables From yaml environment
	envConfig, err := config.NewEnvConfig()
	if err != nil {
		return &licenseManagerTokenCredentialsProvider{}, fmt.Errorf("failed to create LicenseManagerTokenCredentialsProvider, %w", err)
	}
	roleArn := envConfig.RoleARN
	var roleSessionName string
	if envConfig.RoleSessionName == "" {
		roleSessionName = fmt.Sprintf("aws-sdk-go-v2-%v", time.Now().UnixNano())
	} else {
		roleSessionName = envConfig.RoleSessionName
	}
	tokenFilePath := os.Getenv(awsRefreshTokenFilePathEnvVar)
	b, err := ioutil.ReadFile(tokenFilePath)
	if err != nil {
		return &licenseManagerTokenCredentialsProvider{}, fmt.Errorf("failed to create LicenseManagerTokenCredentialsProvider, %w", err)
	}
	refreshToken := aws.String(string(b))

	// 2. Create stsClient
	cfg, err := config.LoadDefaultConfig(context.TODO())
	if err != nil {
		return &licenseManagerTokenCredentialsProvider{}, fmt.Errorf("failed to create LicenseManagerTokenCredentialsProvider, %w", err)
	}
	stsClient := sts.NewFromConfig(cfg, func(o *sts.Options) {
		o.Region = configureStsClientRegion(cfg.Region)
		o.Credentials = aws.AnonymousCredentials{}
	})

	// 3. Configure StsAssumeRoleWithWebIdentityProvider
	stsCredentialProvider := newStsAssumeRoleWithWebIdentityProvider(stsClient, roleArn, roleSessionName, refreshToken)

	// 4. Build and return
	return &licenseManagerTokenCredentialsProvider{
		stsCredentialProvider: stsCredentialProvider,
	}, nil
}

func configureStsClientRegion(configRegion string) string {
	defaultRegion := "us-east-1"
	if configRegion == "" {
		return defaultRegion
	} else {
		return configRegion
	}
}
```