Streamlit in SnowflakeをAzure DevOpsからデプロイする

## 概要
Streamlit in SnowflakeをGitで管理したい！
今回はSnowflake CLIを使って実装しました。

## 使ったもの
- Azure DevOps Repos/Pipelines
- Managed DevOps Pool（DevOpsエージェント）
- Snowflake CLI 3.6.0

:::note info
Azure DevOpsでSnowflakeCLIを手軽に利用できる拡張機能が開発されていますが、2025/4/4現在鋭意開発中の機能ということで採用を見送りました。
:::


## デプロイパイプライン作成
YAMLを使ってパイプラインを作成しました。
Snowflake CLIはpipを使ってインストールしています。
（あまり推奨されていませんが、楽なのでつい……）
```yaml
# cicd-pipeline.yaml
trigger:
- main

pool:
  name: MDP
  demands:
    - ImageOverride -equals ubuntu-latest

variables:
  - group: 'main'
  
stages:
- stage: Checkout_Repository
  jobs:
    - job: Checkout
      displayName: 'Checkout Repository'
      steps:
      - checkout: self
        displayName: 'Checkout repo'
        clean: true

- stage: Deploy
  jobs:
    - job: Deploy
      steps:
      - task: DownloadSecureFile@1
        inputs:
          secureFile: 'rsa_key.p8'
        displayName: 'Download Private_key for snowflake authentication'

      - task: UsePythonVersion@0
        inputs:
          versionSpec: '3.11'

      - script: python -m pip install --upgrade pip setuptools wheel
        displayName: 'Install tools'

      - script: pip install snowflake-cli
        displayName: 'Install snowflake-cli'

      - script: |
          snow streamlit deploy --replace --project $(Build.SourcesDirectory) \
                                --account '$(account)' \
                                --user '$(user)' \
                                --authenticator 'SNOWFLAKE_JWT' \
                                --private-key-file '$(Agent.TempDirectory)/rsa_key.p8' \
                                --database '$(database)' \
                                --schema '$(schema)' \
                                --role '$(role)' \
                                --warehouse '$(warehouse)' \
                                --temporary-connection
        displayName: 'Deploy Streamlit App'
```
SnowflakeのシステムユーザーはキーペアやSSOなどの認証が必要となっています。
今回はキーペア認証を採用しました。
秘密鍵はAzure DevOpsの機能であるセキュアファイルに格納しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3634883/c18565fc-49e7-42d7-b5f0-77114756275d.png)

また、データベース等の情報は変数グループに登録しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3634883/fb6fbdf6-4f0e-44ad-ad5a-a06c721650ea.png)

ゆくゆくはプルリクのトリガーに併せて、デプロイ先を検証環境と本番環境を切り替えられるようにするためです。

### リポジトリ構成
streamlit_project
├── common/
│   └── utils.py
├── page-sec1/
│   └── page1-1.py
├── page-sec2/
│   ├── page2-1.py
│   ├── page2-2.py
│   └── page2-3.py
├── .gitignore
├── cicd-pipeline.yml
├── environment.yml
├── main.py
└── snowflake.yml
今回作成したのはcicd-pipeline.ymlです。
他のファイルも簡単に説明を書きます。
- commonディレクトリ
Pyhonのモジュールです。複数ページで使用する汎用処理を入れておきます。
- page-secディレクトリ
ページを使用用途ごとに分けています。
- environment.yml
必要なPythonのモジュールなどを記載しています
```yaml
# environment.yml
name: sf_env
channels:
  - snowflake
dependencies:
  - streamlit
  - snowflake-snowpark-python
  - pandas
  - plotly
```
- main.py
アプリのエントリポイントです。
```python
#main.py
import streamlit as st

#######################
# ページ設定
#######################
page1_1 = st.Page("./page-sec1/page1-1.py", title="test1-1", icon=":material/deployed_code_alert:")
page2_1 = st.Page("./page-sec2/page2-1.py", title="test2-1", icon=":material/dataset:")
page2_2 = st.Page("./page-sec2/page2-2.py", title="test2-2", icon=":material/dataset:")
page2_3 = st.Page("./page-sec2/page2-3.py", title="test2-3", icon=":material/dataset:")

pg = st.navigation(
    {
        "入力フォーム": [page1_1],
        "データ確認": [page2_1, page2_2, page2_3]
    }
)

def main ():
    pg.run()

###################################
if __name__ == "__main__":
    main()

```
- snowflake.yml
アプリの定義ファイルです。ソースコードを置いておく内部ステージなどを指定します。
```yaml
# snowflake.yml
definition_version: '2'
entities:
  streamlit_project:
    type: streamlit
    identifier:
      name: streamlit_project
      schema: TEST
      database: TEST
    main_file: main.py
    query_warehouse: XSMALL_WH
    stage: CICD_TEST
    artifacts:
      - main.py
      - environment.yml
      - page-sec1/*
      - page-sec2/*
      - common/*
    comment: 'CICD test'
```

### DevOpsパイプラインを実行
以下のようなメッセージが出てデプロイできました。
```
Streamlit successfully deployed and available under https://...
```
Snowflake CLIを使うと内部ステージのデプロイからStreamlitアプリのデプロイが簡単に実行できます。

## やり残していること
`snow streamlit deploy`コマンドは毎回内部ステージにファイル一式をアップロードする工程があります。
ステージに同一名のファイルがある場合は上書き保存されますが、リポジトリから削除したファイルが自動で削除されるわけではありません。
リポジトリのファイル構成とステージのファイル構成の同期を取るには、少し工夫が必要だな～と思い先送りにしてます。。。