iT邦幫忙

2023 iThome 鐵人賽

DAY 8
0
Cloud Native

30 天學習 Pulumi:用各種程式語言控制雲端資源系列 第 8

[Day 08] Pulumi 中的 Input 與 Output 概念 (2)

  • 分享至 

  • xImage
  •  

上一篇文章提到 Pulumi 中,所有資源的屬性都會是 Output 型別,並且是不能直接拿到 Output 的內容。如果要將 Output 內容做處理,必須使用 Output 提供的方法來轉換 Output 成為另一種 Output。

這篇文章就來說明 Output 提供了那些方法可以處理 Output。

Apply

在 Pulumi 中,最常用來處理 Output 的方式就屬 Apply 了,任何的 Output 都可以執行他的 Apply 方法,並且傳遞一個 lambda 函式,用來處理 Output 的值。

例如以下範例:

const webServerUrl: pulumi.Output<string> = 
    eip.publicIp.apply(function (publicIp: string) {
        return "http://" + publicIp + "/";
    });

這這個範例中,我想要使用 Elastic IP 做為 web server 的網址,因此需要在字串前後加點東西。但是 Output 型別不允許我們這麼做,因此我們就可以透過 apply 這個方法來轉換 Output。

apply 接受一個 lambda 函式,參數會是 Output 的值。因此我們就能透過這個 lambda 函式將 public 前後加上 http:///。而這個 lambda 函式的回傳值,最後還會被包裝成 Output 型別。因此我們的 webServerUrl 就會的型別是 Output<string>

pulumi.all

如果我們要組合多個 Output 的值,要怎麼做呢?

我們可以透過 pulumi.all 將多個 Output 轉換為一個 Output。

例如以下範例就會將 myVpc.idmyVpc.cidrBlock 組合成一個 Output combineOutput。可以注意到 combineOutput 的型別為 Output,內裝的是一個陣列,第一個元素為 string,第二個也是 string。

const combineOutput: pulumi.Output<[string, string]> =
    pulumi.all([myVpc.id, myVpc.cidrBlock]);

將多個 Output 組合為一個 Output 後,就能透過 apply 來組合資料了。

const vpcInfo: pulumi.Output<string> = 
    combineOutput.apply(function ([vpcId, vpcCidrBlock]) {
        return vpcId + "   " + vpcCidrBlock
    });

通常來說,使用 pulumi.all 組合多個 Output 成為一個後的目的就是要一起處理這些 Output。因此可以將兩行程式碼改寫為以下:

const vpcInfo: pulumi.Output<string> =
    pulumi.all([myVpc.id, myVpc.cidrBlock])
        .apply(function ([myVpcId, myVpcCidrBlock]) {
            return myVpcId + "   " + myVpcCidrBlock
        });

處理 Input 的方法

之前提到 Input 其實有可能是 3 種型別,純型別(沒有用 Output、Promise 包裝過的型別)、Output、Promise。這樣就會導致我們要處理 Input 的時候會很麻煩。因為我們不清楚使用者會如何使用我們的程式,就需要判斷不同的型別做不同的處理。

回顧一下,前面重構時,類別建構子的參數:

interface CreatePrivateSubnetArgs {
  az: string;
  cidr: string;
  vpcId: pulumi.Input<string>;
  natGatewayId: pulumi.Input<string>;
  defaultTags: Record<string, string>;
}

可以看到 vpcId 是一個 Input,因此使用者在建立 PrivateSubnbet 物件時,vpcId 可能是由 VPC 資源的 Output 取得,也有可能是手動輸入一個 vpc id 字串。

假設因為某些原因,我們需要將 vpcId 做處理,就得判斷使用者傳入的內容是什麼。這會導致程式的複雜。

Pulumi 官方文件中有提到,建議將要處理的 Input 轉換為 Output 進行處理。

這時我們就會用到 pulumi.output 函式了,這個函式是專門將任何的 pulumi.Input 轉換為 pulumi.Output 的函式。

function processVpcId(vpcId: pulumi.Input<string>) {
    let _vpcid = pulumi.output(vpcId);
}

轉換為 Output 後,就可以使用 apply 方法對 output 進行處理,不需要擔心他是純型別、Promise 還是 Output 了。這可以大大簡化對 Input 內容處理的複雜度。

但前面有提到 apply 只能將 Output 轉換為 Output,我們將 Input 變成 Output 以後要怎麼再讓他變回 Input,並傳遞給建立資源的參數呢?

這點倒是不用擔心,因為前面提到,Input 這個型別有三種可能:純型別、Promise、Output。因此轉換後的 Output 其實也是 Input 可以接受的三種型別中的其中一種,是可以直接傳遞到建立資源的參數中不會有問題的。

常見錯誤

以為 apply 後的結果就是純值,或是誤將 apply 的回傳值傳入不支援處理 Apply 的 function

我們來看一個官方提到常見的錯誤

這邊直接引用官方的程式碼來看一下發生什麼事了:

const bucketPolicy = new aws.s3.BucketPolicy("cloudfront-bucket-policy", {
  bucket: contentBucket.bucket,
  policy: JSON.stringify({
        Version: "2012-10-17",
        Statement: [
          {
            Sid: "CloudfrontAllow",
            Effect: "Allow",
            Principal: {
              AWS: iamArn,
            },
            Action: "s3:GetObject",
            Resource: bucket.arn.apply(arn => arn),
          },
        ],
      })
});

這個程式碼是建立一個 S3 的 BucketPolicy,在 Policy 的地方透過 JSON.stringify 將 object 轉換為 string,並傳遞給 policy。
policy 物件中有用到 bucket.arn,且他有使用 apply 對這個 bucket.arn 做處理。

看起來都很正常,這有什麼問題呢?

問題就出在 JSON.stringify 是沒辦法序列化 bucket.arn.apply(arn => arn),因為 apply 還是會回傳 Output,沒有任何方法可以取得 Output 的值,因此 JSON.stringify 也無法取得轉換後的 arn 的值。

那麼要怎麼做才是對的?

  1. 將整個 policy 轉換為 Output

這邊使用 JSON.stringify 是為了將整個 policy 變成字串做為 Input,但我們其實可以將 policy 轉為 Output。

我們可以透過 apply 的 lambda function,將 arn 轉換為 policy。

const policy: pulumi.Output<string> = 
    bucket.arn.apply(function (arn: string) {
        return JSON.stringify({
            Version: "2012-10-17",
            Statement: [
              {
                Sid: "CloudfrontAllow",
                Effect: "Allow",
                Principal: {
                  AWS: iamArn,
                },
                Action: "s3:GetObject",
                Resource: arn,
              },
            ],
        })
    })
  1. 改用 PolicyDocument

查詢Bucket Policy 的文件可以發現,policy 不只可以接受 string,還可以接受 PolicyDocument。PolicyDocument 定義的所有參數都是 pulumi.Input,因此我們不需要煩惱要去轉換 bucket.arn 的值,可以直接這樣使用:

const policy: aws.iam.PolicyDocument = {
    Version: "2012-10-17",
    Statement: [
        {
            Sid: "CloudfrontAllow",
            Effect: "Allow",
            Principal: {
                AWS: iamArn,
            },
            Action: "s3:GetObject",
            Resource: arn,
        }
    ]
};

將 Output 做為資源的 name

第二個常見的錯誤就是將 Output 的值做為資源的 Name。資源的名稱是不支援任何 Input、Output、Promise 的,只能是純文字。因此是無法將 output 做為資源的值使用。

例如:

const publicEc2Name = vpc.id.apply(vpcId => "public-subnet-" + vpcId)

const publicSubnet = new aws.ec2.Subnet(publicEc2Name, ...);

如果一定要這樣用的話,可以在 apply 內部建立資源。

const publicEc2Name = vpc.id.apply((vpcId: string) => {
    const publicSubnet = new aws.ec2.Subnet("public-subnet-" + vpcId, ...);
})

但並不建議在 apply 內部建立資源,因為 apply 的 lambda 執行是非同步的,所以在 preview 產生 diff 的時候可能會有不準確的狀況發生。建議還是想辦法讓值可以固定,不依賴其他輸出。

Output 的設計

Output 的設計理念與非同步程式設計的 Promise 很像,都是一個目前沒有值,之後有值的時候可以透過 apply 去存取值。所以如果比較熟悉以前 JavaScript 操作 Promise 的方法的話,應該會對 Output 的操作不陌生。(會說以前是因為現在都改用 async/await 操作 Promise了)


上一篇
[Day 07] Pulumi 中的 Input 與 Output 概念 (1)
下一篇
[Day 09] Pulumi 中的 Input 與 Output 概念 (3)
系列文
30 天學習 Pulumi:用各種程式語言控制雲端資源30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言