argocd コマンドで別ブランチとの差分を確認する

# ArgoCD の GitOps で Merge 前に manifest の差分を見たい

ArgoCD は Application リソースで source に指定した Git などの定義と実際に Kubernetes クラスタにデプロイされている manifest の差分をリッチな WebUI で確認することができる便利なツールでもありますが、auto sync を有効にした場合は WebUI で差分を確認するといったステップはなく、Git で対象の branch に push されるとそのまま apply されてしまいます。

Pull Request / Merge Request の段階で差分が確認できることが望ましい。

もちろん、auto sync が有効な状態であれば git の内容とデプロイ済みの manifest は同一であるはずなので Git の変更前後の manifest のレンダリング結果の差分を確認すれば良いわけですが、その方法は一旦置いておいて、argocd CLI でクラスタにデプロイ済みの内容と任意の git ref との差分を確認する方法を試してみます。


# Login

まずは、login コマンドで token を取得します。ArgoCD の SSO は多くの IdP に対応しているのでおそらく SSO が有効になっていると思われますが、そんな場合でも `--sso` オプションをつければ大丈夫。

また、Load Balancer が gRPC に対応していなくても `--grpc-web` オプションをつければ大丈夫、これはログイン時に指定しておくとその情報は `~/.config/argocd/config` に書き込まれて以降のコマンドでは省略が可能になります。

```bash
argocd login argocd.example.com --grpc-web --sso
```

複数サーバーを使い分ける場合は `argocd context` コマンドで切り替え可能です。

# Diff

argocd app diff コマンドを試します。
Container image の tag 更新と HPA の上限を上げた mybranch というブランチが存在するとした場合、次のようにして現在 deploy 済みのものとの差分を表示することができます。

```diff
$ argocd app diff mynginx --revision mybranch                                

===== apps/Deployment nginx/mynginx ======
174c174
<       - image: nginx:1.25.5
---
>       - image: nginx:1.27.3

===== autoscaling/HorizontalPodAutoscaler nginx/mynginx ======
81c81
<   maxReplicas: 4
---
>   maxReplicas: 5
```

デフォルトでは差分のある当該行しか表示されませんが、`KUBECTL_EXTERNAL_DIFF` 変数で任意の diff コマンドを指定することが可能です。
指定したコマンドには引数で差分のあるリソースそれぞれについて live manifest と revision で指定された git ref で生成される manifest の file path が渡されます。その為、単に `diff -u` を指定するだけで次のような出力に変わります。

```diff
$ KUBECTL_EXTERNAL_DIFF="diff -u" argocd app diff mynginx --revision mybranch

===== apps/Deployment nginx/mynginx ======
--- /var/folders/6_/3y9s4yg55s97xjvc8tbvgndm0000gn/T/argocd-diff1162649527/mynginx-live.yaml	2024-12-02 23:50:22
+++ /var/folders/6_/3y9s4yg55s97xjvc8tbvgndm0000gn/T/argocd-diff1162649527/mynginx	2024-12-02 23:50:22
@@ -171,7 +171,7 @@
         app.kubernetes.io/name: nginx
     spec:
       containers:
-      - image: nginx:1.25.5
+      - image: nginx:1.27.3
         imagePullPolicy: IfNotPresent
         livenessProbe:
           failureThreshold: 3

===== autoscaling/HorizontalPodAutoscaler nginx/mynginx ======
--- /var/folders/6_/3y9s4yg55s97xjvc8tbvgndm0000gn/T/argocd-diff2265450647/mynginx-live.yaml	2024-12-02 23:50:22
+++ /var/folders/6_/3y9s4yg55s97xjvc8tbvgndm0000gn/T/argocd-diff2265450647/mynginx	2024-12-02 23:50:22
@@ -78,7 +78,7 @@
   resourceVersion: "226417567"
   uid: 294a6291-ee43-401f-a3a2-e0522d56af3b
 spec:
-  maxReplicas: 4
+  maxReplicas: 5
   metrics:
   - resource:
       name: cpu
```

# その他の argocd コマンド

argocd コマンドはほとんどの操作が可能で意外と見やすいですし、ブラウザで開くより便利かもしれない。
ちなみに、ArgoCD のインストールには [headless mode](https://argo-cd.readthedocs.io/en/stable/operator-manual/core/) もというものもあります。

## argocd

https://argo-cd.readthedocs.io/en/latest/user-guide/commands/argocd/

```
$ argocd
argocd controls a Argo CD server

Usage:
  argocd [flags]
  argocd [command]

Available Commands:
  account     Manage account settings
  admin       Contains a set of commands useful for Argo CD administrators and requires direct Kubernetes access
  app         Manage applications
  appset      Manage ApplicationSets
  cert        Manage repository certificates and SSH known hosts entries
  cluster     Manage cluster credentials
  completion  output shell completion code for the specified shell (bash or zsh)
  context     Switch between contexts
  gpg         Manage GPG keys used for signature verification
  help        Help about any command
  login       Log in to Argo CD
  logout      Log out from Argo CD
  proj        Manage projects
  relogin     Refresh an expired authenticate token
  repo        Manage repository connection parameters
  repocreds   Manage repository connection parameters
  version     Print version information
```

## argocd app

https://argo-cd.readthedocs.io/en/latest/user-guide/commands/argocd_app/

```
$ argocd app            
Manage applications

Usage:
  argocd app [flags]
  argocd app [command]

Examples:
  # List all the applications.
  argocd app list

  # Get the details of a application
  argocd app get my-app

  # Set an override parameter
  argocd app set my-app -p image.tag=v1.0.1

Available Commands:
  actions         Manage Resource actions
  add-source      Adds a source to the list of sources in the application
  create          Create an application
  delete          Delete an application
  delete-resource Delete resource in an application
  diff            Perform a diff against the target and live state.
  edit            Edit application
  get             Get application details
  history         Show application deployment history
  list            List applications
  logs            Get logs of application pods
  manifests       Print manifests of an application
  patch           Patch application
  patch-resource  Patch resource in an application
  remove-source   Remove a source from multiple sources application. Counting starts with 1. Default value is -1.
  resources       List resource of application
  rollback        Rollback application to a previous deployed version by History ID, omitted will Rollback to the previous version
  set             Set application parameters
  sync            Sync an application to its target state
  terminate-op    Terminate running operation of an application
  unset           Unset application parameters
  wait            Wait for an application to reach a synced and healthy state
```


## argocd app get

argoce app get の出力例。スッキリしてて良くないですか？

```
$ argocd app get mynginx       
Name:               argocd/mynginx
Project:            myproject
Server:             https://kubernetes.default.svc
Namespace:          nginx
URL:                https://argocd.example.com/applications/mynginx
Source:
- Repo:             https://gitlab.example.com/mygroup/mycharts.git
  Target:           main
  Path:             charts/nginx
  Helm Values:      ../../values/mynginx.yaml
SyncWindow:         Sync Allowed
Sync Policy:        Automated
Sync Status:        Synced to main (1c6031b)
Health Status:      Healthy

GROUP        KIND                     NAMESPACE  NAME     STATUS  HEALTH   HOOK  MESSAGE
             ServiceAccount           nginx      mynginx  Synced                 serviceaccount/mynginx unchanged
             Service                  nginx      mynginx  Synced  Healthy        service/mynginx unchanged
apps         Deployment               nginx      mynginx  Synced  Healthy        deployment.apps/mynginx unchanged
autoscaling  HorizontalPodAutoscaler  nginx      mynginx  Synced  Healthy        horizontalpodautoscaler.autoscaling/mynginx configured
```

## argocd app resources

argocd app resources の出力例

```
$ argocd app resources mynginx
GROUP        KIND                     NAMESPACE  NAME     ORPHANED
             Service                  nginx      mynginx  No
             ServiceAccount           nginx      mynginx  No
apps         Deployment               nginx      mynginx  No
autoscaling  HorizontalPodAutoscaler  nginx      mynginx  No
```

# service account を使って diff を出力

SSO のユーザーアカウントでは CI の処理で使うのに向きません、専用のアカウントを作って実行することにします。

## Helm で account を作成する

helm の values で次のようにして apiKey を利用可能な diff_viewer というアカウントを作成します。ここでの apiKey はそのまま `apiKey` という文字列を指定します。

```yaml:valuesの当該部分
configs:
  cm:
    accounts.diff_viewer: apiKey
  rbac:
    create: true
    policy.default: 'role:readonly'
    policy.csv: |
      p, diff_viewer, applications, get, */*, allow
      g, admin-group, role:admin
```

## Account の確認

Capabilities に apiKey と入っている account が作成されていることがわかります。

```
$ argocd account list                     
NAME         ENABLED  CAPABILITIES
admin        false    login
diff_viewer  true     apiKey
```

```
$ argocd account get --account diff_viewer       
Name:               diff_viewer
Enabled:            true
Capabilities:       apiKey

Tokens:
NONE
```

## Token の作成

`generate-token` コマンドで token を作成します。

```
$ argocd account generate-token --account diff_viewer
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhcmdvY2QiLCJzdWIiOiJkaWZmX3ZpZXdlcjphcGlLZXkiLCJuYmYiOjE3MzMyMjM5MDgsImlhdCI6MTczMzIyMzkwOCwianRpIjoiZTVhNmUyMjYtNmNkOC00YjY1LWJkNGEtMTRlMmRmY2RiZjRlIn0.70UKcOG5nJGpOnFVi_KeI4LsJatYCIoFerliplrp3Co
```

JWT を decode して中身を見てみる

```
Token header
------------
{
  "typ": "JWT",
  "alg": "HS256"
}

Token claims
------------
{
  "iat": 1733223908,
  "iss": "argocd",
  "jti": "e5a6e226-6cd8-4b65-bd4a-14e2dfcdbf4e",
  "nbf": 1733223908,
  "sub": "diff_viewer:apiKey"
}
```

ここで再度 `account get` で確認すると Tokens のところに入っていることがわかります。

```
$ argocd account get --account diff_viewer
Name:               diff_viewer
Enabled:            true
Capabilities:       apiKey

Tokens:
ID                                    ISSUED AT                  EXPIRING AT
e5a6e226-6cd8-4b65-bd4a-14e2dfcdbf4e  2024-12-03T20:05:08+09:00  never
```

## Token を使って diff を取得する

SSO のアカウントではログオフしておくことで token が使われていることを確認します。

```
$ argocd logout argocd.example.com
Logged out from 'argocd.example.com'
```

`app diff` コマンドを実行します。

```diff
$ argocd --server argocd.example.com --grpc-web --auth-token $TOKEN app diff mynginx --revision mybranch

===== apps/Deployment nginx/mynginx ======
174c174
<       - image: nginx:1.25.5
---
>       - image: nginx:1.27.3

===== autoscaling/HorizontalPodAutoscaler nginx/mynginx ======
81c81
<   maxReplicas: 4
---
>   maxReplicas: 5
```

## token の削除

上記で晒してしまったので token を削除しておきます。`account get` で表示されていた ID を指定します。

```
$ argocd account delete-token --account diff_viewer e5a6e226-6cd8-4b65-bd4a-14e2dfcdbf4e
```

token が消えていることを確認

```
$ argocd account get --account diff_viewer
Name:               diff_viewer
Enabled:            true
Capabilities:       apiKey

Tokens:
NONE
```

# おまけ

GitHub には良い感じに Merge Request に差分をコメントで書き込んでくれる便利な actions があるようです。

https://github.com/argocd-diff-action/argocd-diff-action

こちらはなんだか凝っていて実際の ArgoCD インスタンスへのアクセスは不要で、ローカルのクラスタに ArgoCD をセットアップしてそこで差分を出力させるようです。

https://github.com/dag-andersen/argocd-diff-preview