はじめに

さくらのクラウド関連のCLIツールについて、 Chocolateyパッケージの自動ビルド・アップロードをAppVeyorで運用していました。

今回、これらのCI/CDをGitHub Actionsに移行し、あわせて処理の改善を行いました。

この記事では、3つのパッケージの移行作業と、GitHub Actionsで実現した自動化の改善点を記録として残します。

対象パッケージ

移行対象は、次の3つのChocolateyパッケージです。

パッケージ名対象ツール用途
docker-machine-sakuracloudDocker Machine driverさくらのクラウド上にDockerホストを作成
terraform-provider-sakuracloudTerraform providerさくらのクラウドのリソースをTerraformで管理
usacloudCLI clientさくらのクラウドの操作用CLIツール

いずれも、上流リポジトリ(sacloud)の最新リリースを検知し、 Chocolateyパッケージとしてビルド・テスト・アップロードする仕組みです。

3つのパッケージはリポジトリ構成もワークフローもほぼ同じパターンのため、 共通部分をまとめて解説し、差分は個別に記載します。

移行の動機

AppVeyorで運用していた中で、いくつかの課題がありました。

  • スケジュール実行の機能があったものの、AppVeyorに不慣れなため、手動でビルドをトリガー&デプロイする必要があった
  • バージョンの重複チェックが不十分で、Chocolateyの審査中パッケージを考慮できていなかった
  • APIキーがAppVeyorの暗号化変数として埋め込まれており、管理しづらかった
  • ソースコードとCI/CDの設定が別のサービスに分かれていた

GitHub Actionsに移行すれば、cron実行、GitHub Secretsによるキー管理、 ワークフローのコード管理がすべてリポジトリ内で完結します。

リポジトリ構成

3つのリポジトリはすべて同じディレクトリ構成です。

repository/
├── .github/
│   └── workflows/
│       └── chocolatey.yml          # GitHub Actionsワークフロー
├── {package-name}/
│   ├── {package-name}.nuspec       # パッケージメタデータ(XML)
│   └── tools/
│       ├── chocolateyinstall.ps1   # インストールスクリプト
│       ├── chocolateyuninstall.ps1 # アンインストールスクリプト
│       └── LICENSE.txt
├── .gitignore
├── LICENSE
└── README.md

.nuspecとPowerShellスクリプトにはプレースホルダー(__VERSION____HASH32____HASH64__など)を埋め込んでおき、 ビルド時に実際の値へ置換します。

AppVeyor時代のワークフロー

移行前のAppVeyorの設定は、次のような構成でした。

# appveyor.yml(移行前)
version: '{build}'

branches:
  only:
    - master
    - /feature*/

environment:
  CHOCO_API_KEY:
    secure: 53zj0CRv8S/heDM1pmY/...(暗号化されたAPIキー)

build_script:
  - ps: |
      $release = Invoke-RestMethod -Uri "https://api.github.com/repos/sacloud/usacloud/releases/latest"
      $version = $release.tag_name -replace "^v", ""

      # 重複チェック(公開済みのみ)
      $chocoSearch = choco search usacloud --exact --limit-output
      # ... バイナリダウンロード → ハッシュ算出 → パッケージビルド

test_script:
  - ps: choco install usacloud -s .\ -f

deploy_script:
  - ps: choco push $nupkg --source https://push.chocolatey.org/ --api-key $env:CHOCO_API_KEY

artifacts:
  - path: '.\usacloud\*.nupkg'

この構成には、次の問題がありました。

  • スケジュール実行なし
    • 新しいリリースを検知するには手動でビルドをトリガーする必要がある
  • 審査中パッケージの考慮なし
    • choco searchは公開済みパッケージのみを返すため、審査中のパッケージと重複してアップロードしてしまう可能性がある
  • APIキーの管理
    • AppVeyorの暗号化変数としてappveyor.ymlに直接埋め込まれている
  • エンコーディング問題
    • Out-File -Encoding utf8で出力するとBOM付きUTF-8になる
    • しかし、これがWindows PowerShell 5.1で意図どおりに動く場合とそうでない場合がある

GitHub Actionsのワークフロー

トリガー設定

name: Chocolatey パッケージの作成とアップロード

on:
  schedule:
    # 毎日 UTC 0:00 (JST 9:00) に実行
    - cron: "0 0 * * *"
  push:
    branches:
      - master
  workflow_dispatch:
    inputs:
      force:
        description: "既存バージョンのチェックをスキップして強制的にビルド・アップロードする"
        required: false
        type: boolean
        default: false

concurrency:
  group: chocolatey-publish
  cancel-in-progress: false

AppVeyorにはなかった3つのトリガーを設定しています。

  • schedule: 毎日定時に実行し、上流の新リリースを自動検知
  • push: masterブランチへのプッシュで即座に実行
  • workflow_dispatch: 手動実行。forceパラメーターで重複チェックをスキップ可能

concurrency設定で同時実行を防ぎ、パッケージの二重アップロードを回避しています。

パイプライン全体像

ワークフローは8つのステップで構成されています。

  1. Chocolateyのインストールまたはアップグレード
  2. 上流リポジトリの最新バージョン取得
  3. Chocolatey上のバージョン確認(審査中を含む)
  4. バイナリのダウンロードとハッシュ算出
  5. パッケージのビルド
  6. パッケージのテスト
  7. Chocolateyへプッシュ
  8. 成果物のアップロード

ステップ3で最新バージョンが登録済みと判定された場合は、ステップ4以降はスキップされます。

バージョン重複チェック(最大の改善点)

AppVeyor時代はchoco searchで公開済みパッケージのみを確認していました。

GitHub Actionsでは、ChocolateyのOData APIを使い、 審査中(moderation)のパッケージも含めて確認するようにしました。

# OData API で審査中のパッケージも含めて全バージョンを取得
$apiUrl = "https://community.chocolatey.org/api/v2/FindPackagesById()?id='usacloud'"
$response = Invoke-RestMethod -Uri $apiUrl

if ($response) {
  $versions = $response | ForEach-Object {
    $_.properties.Version
  }

  $matched = $versions | Where-Object { $_ -like "$version.*" }
  if ($matched) {
    echo "skip=true" >> $env:GITHUB_OUTPUT
    return
  }
}

OData APIが失敗した場合は、choco searchにフォールバックします。

# フォールバック: choco search で公開済みバージョンのみ確認
$chocoSearch = choco search usacloud --exact --limit-output 2>$null
if ($chocoSearch) {
  $chocoVersion = ($chocoSearch -split '|')[1]
  if ($chocoVersion -like "$version.*") {
    echo "skip=true" >> $env:GITHUB_OUTPUT
    return
  }
}

さらに、forceパラメーターが有効な場合は、 チェック自体をスキップして強制的にビルドを実行します。

この3段階のアプローチにより、審査中パッケージとの重複を防ぎつつ、 API障害時にもフォールバックでき、緊急時には強制実行も可能になりました。

パッケージのビルド

ビルド処理では、プレースホルダーを実際の値に置換してからchoco packを実行します。

$version = "${{ steps.version.outputs.version }}"
$packageVersion = "${version}.${{ github.run_number }}"

# .nuspec のバージョンを置換
(Get-Content '.\usacloud.nuspec' -Raw).Replace("__VERSION__", $packageVersion) |
  Out-File '.\usacloud.nuspec' -Encoding utf8NoBOM

# chocolateyinstall.ps1 のプレースホルダーを置換(BOM付きUTF-8で出力)
$content = (Get-Content '.\tools\chocolateyinstall.ps1' -Raw) `
  .Replace("__VERSION__", "v$version") `
  .Replace("__HASH32__", $hash32) `
  .Replace("__HASH64__", $hash64)
[System.IO.File]::WriteAllText(
  (Resolve-Path '.\tools\chocolateyinstall.ps1').Path,
  $content,
  [System.Text.UTF8Encoding]::new($true)
)

choco pack

パッケージバージョンは{上流バージョン}.{run_number}の形式です。

AppVeyor時代の$env:APPVEYOR_BUILD_VERSIONに相当する部分を、 ${{ github.run_number }}で代替しています。

UTF-8 BOMエンコーディングの対応

移行作業でもっとも苦労したのが、PowerShellスクリプトの文字エンコーディングです。

Chocolateyはchocolateyinstall.ps1Windows PowerShell 5.1で実行します。

Windows PowerShell 5.1BOM付きUTF-8でないと日本語を正しく処理できない場合があるため、 .ps1ファイルはBOM付きUTF-8で出力する必要がありました。

# BOM付きUTF-8で書き出し
[System.IO.File]::WriteAllText(
  $path,
  $content,
  [System.Text.UTF8Encoding]::new($true)  # $true = BOM付き
)

一方、.nuspec(XML)はBOMなしUTF-8で出力します。

そのため、ファイルの種類によって使い分けが必要でした。

この問題は、実際にChocolateyの審査でインストールテストが失敗して発覚し、 数回のコミットで修正を重ねました。

テストとデプロイ

ビルドしたパッケージは、ローカルでインストール・アンインストールテストを行ったうえで、 Chocolateyにプッシュします。

# テスト
choco install usacloud -s .\ -f
usacloud --version
choco uninstall usacloud

# デプロイ
$nupkg = Get-ChildItem .\usacloud\*.nupkg | Select-Object -First 1
choco push $nupkg.FullName --source https://push.chocolatey.org/ --api-key $env:CHOCO_API_KEY

APIキーはGitHub Secretsに格納しており、AppVeyorのように設定ファイルに暗号化キーを埋め込む必要がなくなりました。

パッケージごとの差分

3つのパッケージの主な差分は、次の表のとおりです。

項目usacloudterraform-providerdocker-machine
インストール先PATH上のディレクトリ%APPDATA%\terraform.d\plugins\PATH上のディレクトリ
追加のプレースホルダーなし__URL_32____URL_64__なし
アンインストール処理タブ補完の削除terraform.rcの整理標準
インストール後の検証usacloud --versionなしなし
タブ補完の自動設定ありなしなし

usacloud固有: タブ補完の自動設定

usacloudのインストールスクリプトでは、PowerShellのタブ補完を自動で設定しています。

# PowerShell タブ補完のセットアップ
$usacloudPath = Join-Path $toolsDir 'usacloud.exe'
$completionContent = & $usacloudPath completion powershell

if ($completionContent) {
  $completionContent | Out-File -FilePath $completionScriptPath -Encoding utf8 -Force
  # $PROFILE にドットソース行を追加
  Add-Content -Path $PROFILE -Value "`n. `"$completionScriptPath`""
}

失敗時はusacloud本体のインストールには影響しないよう、try-catchで囲んでいます。

terraform-provider-sakuracloud固有: ダウンロードURLのテンプレート化

terraform-provider-sakuracloudは、リリースごとにダウンロードURLのパターンが異なるため、 URLもプレースホルダーとして管理しています。

# ダウンロードURLもステップ出力として渡す
echo "url32=$url32" >> $env:GITHUB_OUTPUT
echo "url64=$url64" >> $env:GITHUB_OUTPUT

ビルドステップで__URL_32____URL_64__を置換することで、バージョンごとに正しいURLを設定しています。

AppVeyorとGitHub Actionsの比較

移行前後の主な違いをまとめます。

項目AppVeyorGitHub Actions
スケジュール実行なしcron(毎日定時)
手動実行限定的workflow_dispatch + forceパラメーター
バージョン重複チェック公開済みのみ審査中を含む(OData API + フォールバック)
APIキー管理暗号化変数(設定ファイル内)GitHub Secrets
ビルド番号$env:APPVEYOR_BUILD_VERSION${{ github.run_number }}
成果物の保存artifacts セクションupload-artifact v6
エンコーディングOut-File -Encoding utf8UTF8Encoding::new($true)で明示的にBOM付き
同時実行制御max_jobs: 1concurrencyグループ
設定の管理場所別サービスリポジトリ内(.github/workflows/

移行で得られた知見

審査中パッケージの確認は必須

Chocolateyにパッケージをプッシュすると、審査プロセスに入ります。

choco searchでは審査中のパッケージは表示されないため、OData APIを使って審査中のものも含めて確認する必要があります。

これを怠ると、同じバージョンのパッケージを重複してアップロードしてしまい、審査が失敗します。

Windows PowerShell 5.1のエンコーディングに注意

GitHub ActionsのシェルとしてpwshPowerShell 7)を使用していますが、 Chocolateyがインストールスクリプトを実行するのはWindows PowerShell 5.1です。

PowerShell 7ではOut-File -Encoding utf8BOMなしUTF-8を出力しますが、 Windows PowerShell 5.1ではBOM付きUTF-8で入力されることを期待する場合があります。

このギャップにより日本語を含むスクリプトが文字化けするため、 .ps1ファイルは[System.IO.File]::WriteAllText()BOM付きUTF-8を明示的に指定する必要がありました。

フォールバック設計の重要性

外部APIに依存する処理には、フォールバックを設けておくべきです。

今回のバージョンチェックでは、OData APIchoco searchビルド続行という3段階の設計にしたことで、 APIの一時的な障害に影響されなくなりました。

おわりに

3つのChocolateyパッケージのCI/CDをAppVeyorからGitHub Actionsに移行し、 次の改善を実現しました。

  • 毎日の定時実行で上流の新リリースを自動検知
  • 審査中パッケージを含めたバージョン重複チェック
  • GitHub Secretsによるセキュアなキー管理
  • 強制実行モードによる緊急時対応
  • ソースコードとCI/CDの一元管理

3つのリポジトリで共通のワークフローパターンを使っているため、 1つのリポジトリで改善した内容を他のリポジトリにも展開しやすい構成になっています。

参考サイト