---
# System prepended metadata

title: '[零基礎入門] 使用最小 Helm chart，一步一步玩 `with` / `omit` / `toYaml` / `nindent`'
tags: [Go template, template, nindent, YAML, indent, k8s, toYaml, HelmChart, omit, chart, with, K8s / Helm / template, Kubernetes, Helm Chart]

---

[零基礎入門] 使用最小 Helm chart，一步一步玩 `with` / `omit` / `toYaml` / `nindent`
===
###### tags: `K8s / Helm / template`
###### tags: `Kubernetes`, `k8s`, `Helm Chart`, `HelmChart`, `chart`, `template`, `Go template`, `with`, `omit`, `toYaml`, `indent`, `nindent`, `YAML`

<br>

[TOC]

<br>


:::info
## 0. 目標與前置需求

### 目標：

* 建一個**最小 Helm chart**，專門當「Template 實驗室」。
* 不用 K8s cluster，只靠 `helm template` 就能觀察：
  * `with` vs `{{- with`（空白處理差異）
  * `omit` 怎麼從 map 裡排除欄位
  * `toYaml` + `nindent` 怎麼影響輸出 YAML 結構

### 前置需求：

* 已安裝 Helm CLI（`helm version` 能跑起來）
* 有一個工作目錄（例如：`~/work/tpl-lab`）
:::

---

## 1. 建立「超迷你 chart 實驗室」骨架

### Step 1-1. 建立目錄結構

```bash
mkdir -p tpl-lab/templates
cd tpl-lab
```

### Step 1-2. 建立最小 `Chart.yaml`

```bash=
cat > Chart.yaml << 'EOF'
apiVersion: v2
name: tpl-lab
description: Small lab chart to test Helm templates
type: application
version: 0.1.0
appVersion: "1.0.0"
EOF
```

> 這個 chart 不會真的部署，只是給 `helm template` 有東西可以渲染。

<br>

---

<br>

## 2. 準備第一份 `values.yaml`（persistence 測試資料）

建立 `values.yaml`：

```bash=
cat > values.yaml << 'EOF'
controller:
  persistence:
    existingClaim:
      claimName: my-pvc
    storageClassName: nfs-client
    size: 10Gi
    accessModes:
      - ReadWriteOnce
EOF
```

這裡我們有：

* `existingClaim`：一個 map（裡面有 `claimName`）
* `storageClassName`：一個單純字串
* `size` / `accessModes`：一般欄位，待會會用 `omit` 測試

<br>

---

<br>

## 3. 建立 persistence 測試模板

### Step 3-1. 在 `templates/` 裡建 YAML 模板

```bash=
cat > templates/persistence.yaml << 'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
  name: tpl-lab-persistence-test
data:
  config: |
    persistence:
      {{- with .Values.controller.persistence.existingClaim }}
      # existingClaim block
      {{- toYaml . | nindent 6 }}
      {{- end }}{{- /* with existingClaim */}}

      {{- with .Values.controller.persistence.storageClassName }}
      # storageClassName (示範：直接對字串 toYaml)
      {{- toYaml . | nindent 6 }}
      {{- end }}{{- /* with storageClassName */}}

      {{- with omit .Values.controller.persistence "existingClaim" "storageClassName" }}
      # omit 之後剩下的欄位
      {{- toYaml . | nindent 6 }}
      {{- end }}{{- /* with omit ... */}}
EOF
```

> 注意：
>
> * `config: |` 底下多一層縮排，所以我用 `nindent 6`（2 個空白 + 再 4 格）。
> * `{{- ... }}` 前面的 `-` 是為了「吃掉左邊空白/換行」，避免多出空白行。

<br>

---

<br>

## 4. 第一次渲染：觀察整體輸出

在 `tpl-lab` 目錄內執行：

```bash
helm template demo .
```

你會看到其中一段像這樣（局部示意）：

```yaml=
apiVersion: v1
kind: ConfigMap
metadata:
  name: tpl-lab-persistence-test
data:
  config: |
    persistence:
      # existingClaim block
      claimName: my-pvc

      # storageClassName (示範：直接對字串 toYaml)
      nfs-client

      # omit 之後剩下的欄位
      size: 10Gi
      accessModes:
        - ReadWriteOnce
```

### 4-1. 「套用前 / 套用後」局部對照（existingClaim）

**Template（套用前）**

```gotemplate
      {{- with .Values.controller.persistence.existingClaim }}
      # existingClaim block
      {{- toYaml . | nindent 6 }}
      {{- end }}
```

**Values（輸入資料）**

```yaml
existingClaim:
  claimName: my-pvc
```

**渲染後（套用後）**

```yaml
    persistence:
      # existingClaim block
      claimName: my-pvc
```

* `with`：如果存在 → 進 block，把 `.` 換成 `existingClaim` 這個 map。
* `toYaml .`：把 `existingClaim` 轉成 YAML。
* `nindent 6`：前面加一個換行，再縮排 6 格，對齊到 `persistence:` 下面。
    * `nindent` vs `indent`
        * `nindent N`：控制「多一個換行＋縮排 N 格」，讓輸出對齊到正確層級
        * `indent N`：控制「縮排 N 格」，讓輸出對齊到正確層級
    * 情境用途：
        * 想要 **緊貼下一行** → 多半用 `indent`。
        * 想要 **多一個空行再開始內容** → 用 `nindent`。

<br>

---

<br>

## 5. 深入理解 `{{ with }}` vs `{{- with }}`（吃不吃空白）

這裡玩一下空白差異。

### Step 5-1. 改 template：先拿掉 `-`（實驗版）

把 `templates/persistence.yaml` 中的其中一段改成**沒有 `-`** 的版本（先示範一段就好）：

```gotemplate
      {{ with .Values.controller.persistence.existingClaim }}
      # existingClaim block
      {{ toYaml . | nindent 6 }}
      {{ end }}
```

再跑一次：

```bash
helm template demo .
```

你可能會看到類似（局部示意）：

```yaml
    persistence:

      # existingClaim block
      claimName: my-pvc
```

差異：

* 因為 `with` 那一行本身被渲染成「空白行」，沒有被 `-` 吃掉。
* 所以 `persistence:` 和 `# existingClaim block` 中間，多了一行空白。

### Step 5-2. 用 `{{- with` / `{{- end` 清空多餘空白

把它改回：

```gotemplate
      {{- with .Values.controller.persistence.existingClaim }}
      # existingClaim block
      {{- toYaml . | nindent 6 }}
      {{- end }}
```

再跑一次 `helm template demo .`，就回到比較緊實的版本：

```yaml
    persistence:
      # existingClaim block
      claimName: my-pvc
```

**重點小抄：**

* `with`：**邏輯**一樣，不影響內容輸出。
* `{{- with` / `{{- end`：多了 `-` → 把左邊的空白/換行吃掉 → 避免多出空白行。

<br>

---

<br>

## 6. 深入理解 `omit`：把特定 key 排除掉

看這段：

```gotemplate
      {{- with omit .Values.controller.persistence "existingClaim" "storageClassName" }}
      # omit 之後剩下的欄位
      {{- toYaml . | nindent 6 }}
      {{- end }}
```

### 6-1. 套用前：原始 map

原始的 `controller.persistence` 是：

```yaml
existingClaim:
  claimName: my-pvc
storageClassName: nfs-client
size: 10Gi
accessModes:
  - ReadWriteOnce
```

### 6-2. `omit` 做了什麼？

```gotemplate
omit .Values.controller.persistence "existingClaim" "storageClassName"
```

概念上就是：

> 從這個 map 裡，**排除** `existingClaim` 和 `storageClassName` 這兩個 key。

所以 `omit` 後得到的 map 概念上變成：

```yaml
size: 10Gi
accessModes:
  - ReadWriteOnce
```

### 6-3. 套用後：搭配 `with + toYaml + nindent`

整段：

```gotemplate
{{- with omit .Values.controller.persistence "existingClaim" "storageClassName" }}
# omit 之後剩下的欄位
{{- toYaml . | nindent 6 }}
{{- end }}
```

* `omit ...` → 得到只含 `size` / `accessModes` 的 map。
* `with` → 如果 map 非空 → 進 block，`.` 變成「那個 map」。
* `toYaml .` → 轉成 YAML：

  ```yaml
  size: 10Gi
  accessModes:
    - ReadWriteOnce
  ```
* `nindent 6` → 對齊到 `persistence:` 下面。

**渲染結果（局部）**

```yaml
    persistence:
      # existingClaim block
      claimName: my-pvc

      # storageClassName (示範：直接對字串 toYaml)
      nfs-client

      # omit 之後剩下的欄位
      size: 10Gi
      accessModes:
        - ReadWriteOnce
```

> 這招很適合：「某幾個欄位要特別處理，其餘全部交給 `toYaml` 一次吐出」。

<br>

---

<br>

## 7. 額外練習：values 不同情境（nil / 少欄位）

### Step 7-1. 建立「沒有 existingClaim」的 values

```bash
cat > values-no-claim.yaml << 'EOF'
controller:
  persistence:
    storageClassName: nfs-client
    size: 5Gi
EOF
```

執行：

```bash
helm template demo . -f values-no-claim.yaml
```

你會看到：

* `with .Values.controller.persistence.existingClaim`
  → 因為 `existingClaim` 不存在 / 為空 → **整個 block 不輸出**。
* `omit .Values.controller.persistence "existingClaim" "storageClassName"`
  → 只會輸出剩下的欄位（這裡只剩 `size`）。

**渲染結果（局部示意）：**

```yaml
    persistence:
      # storageClassName (示範：直接對字串 toYaml)
      nfs-client

      # omit 之後剩下的欄位
      size: 5Gi
```

<br>

---

<br>

## 8. 額外實驗：`indent` vs `nindent`

### Step 8-1. 新增 `nindent-test.yaml`

```bash=
cat > templates/nindent-test.yaml << 'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
  name: nindent-test
data:
  with-indent: |
{{ toYaml .Values | indent 4 }}
  with-nindent: |
{{ toYaml .Values | nindent 4 }}
EOF
```

再跑：

```bash
helm template demo .
```

你會看到（局部示意）：

```yaml=
data:
  with-indent: |
    controller:
      persistence:
        existingClaim:
          claimName: my-pvc
        storageClassName: nfs-client
        size: 10Gi
        accessModes:
          - ReadWriteOnce
  with-nindent: |

    controller:
      persistence:
        existingClaim:
          claimName: my-pvc
        storageClassName: nfs-client
        size: 10Gi
        accessModes:
          - ReadWriteOnce
```

**差異：**

* `indent 4`：只幫你縮排 4 個空白，不會自動加前置換行。
* `nindent 4`：會先加一個「換行」，再縮排 4 個空白。
  → 你會看到 `with-nindent` 底下多一行空白。

**小抄：**

* 用在 `key: |` 後面時：

  * 想要 **緊貼下一行** → 多半用 `indent`。
  * 想要 **多一個空行再開始內容** → 用 `nindent`。

<br>

---

<br>

## 9. 建議的練習路線（把這個實驗室當 REPL 用）

之後你要玩其他 Helm 函式（`include` / `tpl` / `default` / `required`…）時，可以照這套路：

1. **固定使用 `tpl-lab` 這個 chart** 當實驗場。
2. 新增或修改 `templates/xxx.yaml`，專門測某一個語法。
3. 用不同 `values-*.yaml` 搭配 `-f` 切換情境：

   ```bash
   helm template demo . -f values.yaml
   helm template demo . -f values-no-claim.yaml
   ```
4. 如果 chart 以後變大，可以用：

   ```bash
   helm template demo . --show-only templates/persistence.yaml
   ```

   只看單一模板的輸出。

---

如果你接下來想把 `storageClassName` 那段改成「更語意正確」（輸出 `storageClassName: nfs-client` 而不是孤零零 `nfs-client`），我也可以幫你再寫一個「原始寫法 vs 改良寫法」的對照版實驗章節。

<br>

---

<br>

## 討論
### `{{ with` 前面的數個空白會被吃掉？
- ### Question
    ```gotemplate
          {{ with .Values.controller.persistence.existingClaim }}#這裡是註解
    ```
    {{ 前面的空白會保留？還是整行是空白行？
- ### 結論：渲染結果
    ```
          #這裡是註解
    ```
- ### gpt-5.1 (2025/11/28)
    會保留，而且**會真的輸出那些空白**，只是因為 `with` 這行本身不產生任何文字，所以最後看起來像「只有空白的空行」。

    我們拆開來看這一行：

    ```gotemplate
          {{ with .Values.controller.persistence.existingClaim }}
    ```

    * `      ` ← 這 6 個空白是「純文字」，Go template / Helm 不會幫你吃掉
    * `{{ with ... }}` ← 這個動作本身不輸出任何內容（只是改變 `.` 和控制之後的區塊要不要執行）

    ### 實際效果示意

    - 假設 template 是：

        ```gotemplate
        persistence:
              ${{ with .Values.controller.persistence.existingClaim }}
              {{ toYaml . | nindent 6 }}  #comment
              {{ end }}&
        next-key: xxx
        ```
        - `{{with}}` 和 `{{end}}`  前後用字元 `$`、`&` 來當作分隔符號，易於判斷空白部份

    - 而 `.Values.controller.persistence.existingClaim` **不存在 / 為空** 時，整個 `with` block 都不輸出內容，結果會變成：

        ```yaml
        persistence:
              $&
        next-key: xxx
        ```
        - 中間那一行實際上是：
            ```text
            <六個空白>$&<換行>
            ```
            - `<換行>`: `&` 到下一行`next-key:`中間有 `\n`
        - `#comment` 位於 `{{with}}` 和 `{{end}}` 區間，因 key 不存在而不輸出

    - 也就是你問的那句：

        > `{{` 前面的空白會保留？還是整行是空白行？

        答案是：

        * **空白會保留**，所以這一行會變成「只包含空白的行」。
        * 在 YAML 看起來就像「空白行」，但技術上裡面是有空白字元的。

    <br>
    
    ---

    ### 如果改成 `{{- with ... }}` 會怎樣？

    若寫成：

    ```gotemplate
          {{- with .Values.controller.persistence.existingClaim }}
    ```

    那就不一樣了：

    * `{{-` 會把「在 `{{` 左邊的空白＋換行」全部吃掉
    * 實際上會連同**上一行結尾的換行 + 這一行開頭的空白**一起清掉

    常見 pattern 是讓這種「只有控制語法的行」完全不留下視覺上的空行，因此你會看到很多 Helm 寫法長這樣：

    ```gotemplate
    persistence:
    {{- with .Values.controller.persistence.existingClaim }}
      {{- toYaml . | nindent 2 }}
    {{- end }}
    next-key: xxx
    ```

    這樣如果 `with` 沒進去，就 **不會多出任何空白行**。

    ---

    如果你願意，下一步我可以幫你把你現在那一段 `persistence` 全部改寫成一個「不會產生孤零零 value、也不會亂多空行」的版本，順便標註哪一行在「輸出前 / 輸出後」長什麼樣子。


<br>

{%hackmd vaaMgNRPS4KGJDSFG0ZE0w %}
