Podman のREST APIを使ってコンテナ内にファイルを配置する

# Podman REST APIを使用してコンテナにファイルをコピーする方法

## はじめに

Podmanは、コンテナ管理のための強力なツールです。本記事では、PythonからPodman REST APIを使用してコンテナにファイルをコピーする方法について解説します。

特に、公式ドキュメントには記載されていない重要な点について詳しく説明します。


## Podman REST APIの基本

Podman はコンテナのためのツールですが REST API を提供しており、プログラマブルに制御できます。ここでは Python で扱える [podmanモジュール](https://pypi.org/project/podman/)を使用します。

このモジュールは基本的な操作をサポートしていますが、コンテナ内にファイルをコピーするAPIは含まれていません。


## コンテナへのファイルコピーの実現

モジュールにない操作は[Podman HTTP APIドキュメント](https://docs.podman.io/en/latest/_static/api.html?version=v5.0#tag/containers/operation/PutContainerArchiveLibpod)を参照すれば実装は難しくありません。[ファイルコピーについても記載されています。](https://docs.podman.io/en/latest/_static/api.html?version=v5.0#tag/containers/operation/PutContainerArchiveLibpod)

しかし、ファイルコピーについてはドキュメントの記載が間違っており、その通りにやっても動作しません。

![Podman Issue Screenshot](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38967/cbacc447-64e8-2f8b-71ff-cf24612caa1b.png)


### 問題点

結論から言うと、ファイルコピーについてはドキュメントに記載されているContent-Typeが正しくありませんでした。これに類似する問題については、[Podmanのissue #21861](https://github.com/containers/podman/issues/21861)でも報告されています。

正常だと主張する HTTP Status を返すものの、求める動作をしていない状況です。


### 解決策

ひとつひとつトラブルシューティングを行いました。

まずは podman の cli が動作するだろうと考え、以下のコマンドで正常にファイルをコピーできることを確認しました。

```shell-session
podman container cp ./trial.py sandbox:/home
```

コピーの確認：

```shell-session
podman container exec -it sandbox /bin/bash
# ls -al /home
```


### CLIの実装調査

動作確認が取れたので cli のコードを読んでいきます。

Podmanのソースコードを調査した結果、[containers/archive.go](https://github.com/containers/podman/blob/48e727f1b47899512f6ee7f402ca7d19eb6bf441/pkg/bindings/containers/archive.go#L72)が該当する処理を行っていることが分かりました。

DoRequest で HTTP リクエストを送っています。[DoRequestの実装](https://github.com/containers/podman/blob/48e727f1b47899512f6ee7f402ca7d19eb6bf441/pkg/bindings/connection.go#L257)を見るとheaderを受け取るようになっていますが、archive.goからはnilが渡されていました。

このことから、ドキュメントに記載があったJSON形式での送信は不要であることが判明しました。


## Pythonによる実装例

原因が判明したので requests モジュールで実装します。

以下に、PythonでPodman REST APIを使用してコンテナにファイルをコピーする完全な実装例を示します

```python
from podman import PodmanClient
import tarfile
import io

def create_tar_string(file_name: str, file_content: str) -> str:
    """tarファイルを作成し、文字列として返す"""
    buffer = io.BytesIO()
    
    with tarfile.open(fileobj=buffer, mode="w") as tar:
        info = tarfile.TarInfo(name=file_name)
        info.size = len(file_content)
        file_bytes = file_content.encode('utf-8')
        
        tar.addfile(info, io.BytesIO(file_bytes))    
    tar_string = buffer.getvalue().decode('utf-8')
    return tar_string

uri = "unix:///run/user/1000/podman/podman.sock"
with PodmanClient(base_url=uri) as client:
    version = client.version()
    print("Release: ", version["Version"])
    print("Compatible API: ", version["ApiVersion"])
    print("Podman API: ", version["Components"][0]["Details"]["APIVersion"], "\n")

    image_name = "docker.io/library/python:3.11-slim"
    client.images.pull(image_name)

    container_name = "sandbox"
    container = client.containers.create(
        image_name,
        name="sandbox",
        command=["python", "-c", "import time; time.sleep(3600)"],  # コンテナを起動したまま保持
        detach=True
    )

    # コンテナの起動
    container.start()

    # ファイルコピーするために tarfile を作成
    tar_string = create_tar_string("trial.py", "print('Hello, World!')")
    
    # 【ファイルコピー】
    response = client.api.put(f"/containers/{container_name}/archive", params={"path": "/home"}, data=tar_string)
    print(response.status_code)
    print(response.text)

    # コンテナ内で転送したファイルをそのまま実行
    exit_code, output = container.exec_run("python /home/trial.py")
    print(f"Exit code: {exit_code}")
    print(f"Output: {output.decode()}")

    # コンテナの停止と削除
    container.stop()
    container.remove()

```

このコードは以下の手順で動作します：

1. Podmanクライアントの初期化
2. Pythonイメージのプル(docker.ioは省略できません)
3. コンテナの作成と起動
4. 転送したいファイルの Tar file 化
5. ファイルのコピー
6. コピーしたファイルを実行
7. コンテナの停止と削除

## 重要なポイント

1. ファイルコピーAPIではPUTメソッドでの通信になります。またJSONを送信しません
2. `create_tar_string` 関数を使用して、コピーするファイルをtar形式に変換します
3. ファイルコピーには`client.api.put`メソッドを使用し、クエリパラメータとしてコピー先のパスを指定します
4. ファイルが正しくコピーされなかった場合でも200ステータスコードが返される可能性があるため、実際にファイルが存在するか確認することをおすすめします


## まとめ

Podman REST APIを使用してコンテナにファイルをコピーする方法について解説しました。公式ドキュメントには記載されていない重要な点もありますが、本記事で紹介した方法を使用することで、ファイルコピーを実装できます。