GitLab CI/CD: artifacts:expire_inで変数が使えずブランチごとに有効期限を変えられない件とその回避策

# GitLab CI/CD: artifacts有効期限をブランチごとに設定する3つの方法

## はじめに

GitLab CI/CDにてブランチごとに成果物 (artifacts) の保存期間を変えたいと考え、実現しようとしたところ、いくつかの課題に直面しました。例えば、`main` ブランチや `develop` ブランチの成果物は長く保持し、フィーチャーブランチのものは短期間で削除したい、といったケースです。

直感的には `.gitlab-ci.yml` の `artifacts:expire_in` に変数を使えば実現できそうなのですが、残念なことに GitLab CI/CD の現在の仕様 (2025年4月時点) では、`expire_in` キーで CI/CD 変数（事前定義変数やカスタム変数）を直接展開することはできません。

本記事では、この制約と、その回避策について情報を共有します。

## やりたいこと：ブランチによる Artifacts 有効期限の動的変更

理想としては、以下のように `.gitlab-ci.yml` を記述して、ブランチ名に応じて `expire_in` の値を動的に変えたいと考えられます。

```yaml
variables:
  # ブランチに応じて有効期限を変えたい（例）
  ARTIFACT_EXPIRATION: '1 day' # デフォルトは1日

build_job:
  stage: build
  script:
    - mkdir output
    - echo "artifact content" > output/artifact.txt
  artifacts:
    paths:
      - output/
    # !!! これは期待通りに動作しない !!!
    expire_in: $ARTIFACT_EXPIRATION
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      variables:
        ARTIFACT_EXPIRATION: '30 days' # mainブランチなら30日
    - when: on_success # それ以外のブランチ (デフォルト値 '1 day' を期待)
```

## 問題点：`expire_in` では変数が展開されない

上記の YAML を記述しても、GitLab CI/CD は `expire_in: $ARTIFACT_EXPIRATION` の部分で変数を展開しません。 `$ARTIFACT_EXPIRATION` という **文字列** を期間として解釈しようとするため、無効な値とみなされ、意図した有効期限になりません。

これは、`.gitlab-ci.yml` の解析段階で `expire_in` の値が静的に決定される必要があるため、あるいは特定の期間フォーマット文字列（例: `1 week`, `30 days`, `never`）のみを受け付ける仕様になっているためと考えられます。

## 回避策

`expire_in` で直接変数が使えない以上、別の方法で要件を実現する必要があります。以下に3つの方法を説明します。

### 1. `rules` を使ってジョブ定義を分ける

`rules` (または古い `only/except`) を使用して、有効期限が異なるジョブをブランチごとに定義します。

```yaml
# .gitlab-ci.yml

build_feature_branch:
  stage: build
  script:
    - mkdir output && echo "feature artifact" > output/artifact.txt
  artifacts:
    paths: [output/]
    expire_in: 1 day # Featureブランチは1日で失効
  rules:
    # main と develop 以外の場合に実行
    - if: '$CI_COMMIT_BRANCH != "main" && $CI_COMMIT_BRANCH != "develop"'

build_main_or_develop:
  stage: build
  script:
    - mkdir output && echo "main/develop artifact" > output/artifact.txt
  artifacts:
    paths: [output/]
    expire_in: 4 weeks # main/developブランチは4週間保持
  rules:
    # main または develop の場合に実行
    - if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "develop"'
```

**メリット:**

* YAML の中で完結し、わかりやすい。
* GitLab の標準機能のみで実現できる。

**デメリット:**

* 共通部分が多い場合、ジョブ定義が冗長になる。
    * (`extends` で緩和できる。)

### 2. GitLab API を使ったクリーンアップジョブ

GitLab API を利用して定期的に古いアーティファクトを削除するスケジュールジョブを作成します。

1.  **スケジュールパイプラインを設定:** 特定の頻度（例: 毎日深夜）で実行されるパイプラインを作成。
2.  **API クリーンアップジョブ:**
    * GitLab API (Project -> Jobs -> Artifacts API など) を呼び出す。
    * スクリプト (例: Python, Shell) で、特定の条件に合致するアーティファクトをリストアップし、API 経由で削除。
    * API 認証のために、適切な権限を持つトークンが必要。

```yaml
# 例：スケジュール実行用のクリーンアップジョブ
cleanup_old_artifacts:
  stage: cleanup
  image: curlimages/curl # または Python イメージなど
  script:
    # APIを叩くスクリプト (要実装)
    - ./scripts/cleanup_artifacts.sh $GITLAB_API_TOKEN $CI_PROJECT_ID
  rules:
    # スケジュール実行の場合のみ動作
    - if: '$CI_PIPELINE_SOURCE == "schedule"'
```

**メリット:**

* 柔軟な条件でクリーンアップ可能。
* `expire_in` に依存しない。

**デメリット:**

* API スクリプトの実装とメンテナンスが必要。
* API トークンの管理が必要。
* 設定が複雑になる。

### 3. アーティファクト保存専用ジョブを作成する

アーティファクトをダウンロードし、異なる `expire_in` を設定して再度アップロードするジョブを定義します。

1.  **ビルドジョブ:** アーティファクトを作成。`expire_in` は短くするか、`never` に設定。
2.  **保存用ジョブ:**
    * ビルドジョブの完了後に実行されるように `needs` を設定。
    * 特定のブランチでのみ実行されるように `rules` を設定。
    * ビルドジョブと同じ `paths` を指定。
    * `expire_in` に保持したい期間を設定。
    * `script` でアーティファクトのダウンロードを確認。

```yaml
stages:
  - build
  - store_artifacts # アーティファクト保存用のステージを追加

build_job:
  stage: build
  script:
    - mkdir output
    - echo "artifact content" > output/artifact.txt
  artifacts:
    paths:
      - output/
    expire_in: 1 hour

store_artifacts_feature:
  stage: store_artifacts
  script:
    - ls -R # アーティファクトを確認
  artifacts:
    paths:
      - output/
    expire_in: 1 day # Featureブランチ用の保存期間
  needs:
    - job: build_job
      artifacts: true
  rules:
    - if: '$CI_COMMIT_BRANCH != "main" && $CI_COMMIT_BRANCH != "develop"'

store_artifacts_main_develop:
  stage: store_artifacts
  script:
    - ls -R
  artifacts:
    paths:
      - output/
    expire_in: 4 weeks # main/developブランチ用の保存期間
  needs:
    - job: build_job
      artifacts: true
  rules:
    - if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "develop"'
```

**メリット:**

* ビルドと保存ポリシーを分離できる。
* ビルドジョブの YAML が複雑になりにくい。

**デメリット:**

* ジョブ数が増える。
* ダウンロードと再アップロードのオーバーヘッドがある。
* `needs` の設定が必要。

## まとめ

GitLab CI/CD の `artifacts:expire_in` では、現時点 (2025年4月時点) で CI/CD 変数を直接利用できません。そのため、ブランチごとにアーティファクトの有効期限を動的に変更するには、以下のいずれかの回避策を検討する必要があります。

1.  `rules` を使ったジョブ定義の分割
2.  GitLab API を利用したクリーンアップジョブ
3.  アーティファクト保存専用ジョブの作成

将来的には GitLab CI/CD の仕様が改善され、`expire_in` での変数展開がサポートされることを期待しますが、それまではプロジェクトの状況に合わせて最適な方法を選択してください。

## 参考

* GitLab CI/CD `artifacts:expire_in` documentation: [https://docs.gitlab.com/ee/ci/yaml/\#artifactsexpire\_in](https://docs.gitlab.com/ee/ci/yaml/#artifactsexpire_in)
* GitLab CI/CD `rules` documentation: [https://docs.gitlab.com/ee/ci/yaml/\#rules](https://docs.gitlab.com/ee/ci/yaml/#rules)
* GitLab API documentation: [https://docs.gitlab.com/ee/api/](https://docs.gitlab.com/ee/api/)