iT邦幫忙

2023 iThome 鐵人賽

DAY 9
0
Cloud Native

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

[Day 09] Pulumi 中的 Input 與 Output 概念 (3)

  • 分享至 

  • xImage
  •  

續談 Output 的用法

前一篇文章介紹了一些 Output 的常用處理方式,包含 applypulumi.allpulumi.output。今天再來看一些範例與技巧。

在使用 AWS EKS 的時候,如果我們要自行新增機器,不是透過 EKS 的 NodeGroup 的時候,通常會使用 AWS EKS 最佳化的 AMI。該 AMI 通常要提供 UserData 讓 EC2 開機的時候可以註冊到 EKS Cluster 中。

這邊有個函式是用來生成 UserData:

function generateUserDataForEksNode(
  clusterName: pulumi.Input<string>,
  clusterCA: pulumi.Input<string>,
  apiServerEndpoint: pulumi.Input<string>
): pulumi.Output<string> {
  return pulumi
    .all([clusterName, clusterCA, apiServerEndpoint])
    .apply(function ([clusterName, clusterCA, apiServerEndpoint]) {
      return `#!/bin/bash
set -ex
B64_CLUSTER_CA=${clusterCA}
API_SERVER_URL=${apiServerEndpoint}
/etc/eks/bootstrap.sh ${clusterName} --b64-cluster-ca $B64_CLUSTER_CA --apiserver-endpoint $API_SERVER_URL\`;
      `;
    });
}

首先這邊用到了第一個技巧,就是這個函式的所有輸入參數都是 pulumi.Input 型別。這樣我們就不用煩惱使用者是傳遞原始型別 (primitive types)、還是其他的 Output 給我們。

第二個技巧是使用 pulumi.all 將傳入的參數轉換為單一 Output 處理。上一篇文章有提到,可以使用 pulumi.output 將任何數值轉換為 Output 類型。其實 pulumi.all 也可以達到一樣的效果,

pulumi.all 的型別定義比較複雜,這邊附上這個範例所用到的定義:

export declare function all<T1, T2, T3>(values: [Input<T1>, Input<T2>, Input<T3>]): Output<[Unwrap<T1>, Unwrap<T2>, Unwrap<T3>]>;

可以看到 pulumi.all 實際上接受的是 Input 陣列,並且會轉成 Output 型別。

Lifting

當我們有 generateUserDataForEksNode 函式的時候我們要怎麼使用呢?

這邊假設我們有個 EKS cluster,我們可以從這個 cluster 中提取 name, endpoint, certificateAuthorities,用於 generateUserDataForEksNode 的參數。

const cluster = new aws.eks.Cluster("my-eks", {...});

const clusterName: pulumi.Output<string> = cluster.name;
const clusterApiEndpoint: pulumi.Output<string> = cluster.endpoint;
const clusterCA: pulumi.Output<string> = 
    cluster.certificateAuthorities.apply(cas => cas[0].data);

const userData = generateUserDataForEksNode(clusterName, clusterCA, clusterApiEndpoint);

從這個範例可以看到,需要特別為 certificateAuthorities 做處理。因為 ClusterCertificateAuthority 的型別是 Output<ClusterCertificateAuthority[]>,但這邊需要的是 ClusterCertificateAuthority 裡面的 data,因此需要使用 apply 將 Output<ClusterCertificateAuthority[]> 中的 data 拿出來。

如果很多這種巢狀的 Output 都要這樣處理,其實寫起來很不方便。因此 pulumi 提供了一個方法稱為 lifting。我們可以透過 lifting 的方式快速存取巢狀物件內的資料。

例如剛剛的 certificateAuthorities 轉換可以直接改為:

const clusterCA: pulumi.Output<string> = 
    cluster.certificateAuthorities[0].data;

Concat

在 Pulumi 中,經常會有將多個 Input 型別的資料組合成一個新的字串的需求,Pulumi 也提供了一個方便的方式可以快速處理這種需求。透過 pulumi.concat 即可快速組合 Input 成為新的 Output。

一樣是產生 user data 的範例,改用 pulumi.concat 撰寫後變成這樣:

function generateUserDataForEksNode(
  clusterName: pulumi.Input<string>,
  clusterCA: pulumi.Input<string>,
  apiServerEndpoint: pulumi.Input<string>
): pulumi.Output<string> {

  return pulumi.concat("#!/bin/bash\n",
    "set -ex\n",
    "B64_CLUSTER_CA=", clusterCA, "\n",
    "API_SERVER_URL=", apiServerEndpoint, "\n",
    "/etc/eks/bootstrap.sh ", clusterName, " --b64-cluster-ca $B64_CLUSTER_CA --apiserver-endpoint $API_SERVER_URL\n");
}

Interpolate

在 Output 這章最後介紹一個只在 JavaScript/TypeScript 才有的功能:pulumi.interpolate

這個功能是利用 ES6 的 tagged templates 達成的。

先來看範例:

function generateUserDataForEksNode(
  clusterName: pulumi.Input<string>,
  clusterCA: pulumi.Input<string>,
  apiServerEndpoint: pulumi.Input<string>
): pulumi.Output<string> {
  return pulumi.interpolate`#!/bin/bash
set -ex
B64_CLUSTER_CA=${clusterCA}
API_SERVER_URL=${apiServerEndpoint}
/etc/eks/bootstrap.sh ${clusterName} --b64-cluster-ca $B64_CLUSTER_CA --apiserver-endpoint $API_SERVER_URL\\
  `
}

可以看到與一開始的版本大同小異,唯一的差別在於沒有了 pulumi.all([...]).apply() 的結構了,使用 pulumi.interpolate 取代。

這邊要注意,這是 ES6 的一種新寫法,pulumi.interpolate 後直接連接著 Template Literals,不是呼叫函式的時候忘記加小括號。

這個 tagged template 的功能有點像 pulumi.concat 的語法糖,ES6 執行時會把 Template 中會用到的變數蒐集成一個參數,因此在 interpolate 的實作中,就可以使用前面的技巧組合出 Output<string>

這邊附上 interpolate 的原始碼,可以看到內部是使用 pulumi.output 加上 pulumi.apply 的方式產生輸出。

function interpolate(literals, ...placeholders) {
    return output(placeholders).apply((unwrapped) => {
        let result = "";
        // interleave the literals with the placeholders
        for (let i = 0; i < unwrapped.length; i++) {
            result += literals[i];
            result += unwrapped[i];
        }
        // add the last literal
        result += literals[literals.length - 1];
        return result;
    });
}

其他語言中,有提供類似的方式,例如在 Python 可以使用 Output.format,Golang 中可以使用 pulumi.Sprintf。不同語言如何使用可以參考官方文件

後記

本來今天要順便講 Stack Output,發現寫的內容比預想中的多,就留到明天再發好了。連續創作了一個禮拜,有點習慣這種感覺,但每天都要花 2~3 個小時寫文章趕 deadline。希望之後可以開始累積一些草稿,就能讓文章品質高一點了。


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

尚未有邦友留言

立即登入留言