はじめに
さくらのクラウド関連のCLIツールについて、 Chocolateyパッケージの自動ビルド・アップロードをAppVeyorで運用していました。
今回、これらのCI/CDをGitHub Actionsに移行し、あわせて処理の改善を行いました。
この記事では、3つのパッケージの移行作業と、GitHub Actionsで実現した自動化の改善点を記録として残します。
対象パッケージ
移行対象は、次の3つのChocolateyパッケージです。
| パッケージ名 | 対象ツール | 用途 |
|---|---|---|
| docker-machine-sakuracloud | Docker Machine driver | さくらのクラウド上にDockerホストを作成 |
| terraform-provider-sakuracloud | Terraform provider | さくらのクラウドのリソースをTerraformで管理 |
| usacloud | CLI 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に直接埋め込まれている
- AppVeyorの暗号化変数として
- エンコーディング問題
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つのステップで構成されています。
- Chocolateyのインストールまたはアップグレード
- 上流リポジトリの最新バージョン取得
- Chocolatey上のバージョン確認(審査中を含む)
- バイナリのダウンロードとハッシュ算出
- パッケージのビルド
- パッケージのテスト
- Chocolateyへプッシュ
- 成果物のアップロード
ステップ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.ps1をWindows PowerShell 5.1で実行します。
Windows PowerShell 5.1はBOM付き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つのパッケージの主な差分は、次の表のとおりです。
| 項目 | usacloud | terraform-provider | docker-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の比較
移行前後の主な違いをまとめます。
| 項目 | AppVeyor | GitHub Actions |
|---|---|---|
| スケジュール実行 | なし | cron(毎日定時) |
| 手動実行 | 限定的 | workflow_dispatch + forceパラメーター |
| バージョン重複チェック | 公開済みのみ | 審査中を含む(OData API + フォールバック) |
| APIキー管理 | 暗号化変数(設定ファイル内) | GitHub Secrets |
| ビルド番号 | $env:APPVEYOR_BUILD_VERSION | ${{ github.run_number }} |
| 成果物の保存 | artifacts セクション | upload-artifact v6 |
| エンコーディング | Out-File -Encoding utf8 | UTF8Encoding::new($true)で明示的にBOM付き |
| 同時実行制御 | max_jobs: 1 | concurrencyグループ |
| 設定の管理場所 | 別サービス | リポジトリ内(.github/workflows/) |
移行で得られた知見
審査中パッケージの確認は必須
Chocolateyにパッケージをプッシュすると、審査プロセスに入ります。
choco searchでは審査中のパッケージは表示されないため、OData APIを使って審査中のものも含めて確認する必要があります。
これを怠ると、同じバージョンのパッケージを重複してアップロードしてしまい、審査が失敗します。
Windows PowerShell 5.1のエンコーディングに注意
GitHub Actionsのシェルとしてpwsh(PowerShell 7)を使用していますが、
Chocolateyがインストールスクリプトを実行するのはWindows PowerShell 5.1です。
PowerShell 7ではOut-File -Encoding utf8がBOMなしUTF-8を出力しますが、
Windows PowerShell 5.1ではBOM付きUTF-8で入力されることを期待する場合があります。
このギャップにより日本語を含むスクリプトが文字化けするため、
.ps1ファイルは[System.IO.File]::WriteAllText()でBOM付きUTF-8を明示的に指定する必要がありました。
フォールバック設計の重要性
外部APIに依存する処理には、フォールバックを設けておくべきです。
今回のバージョンチェックでは、OData API → choco search → ビルド続行という3段階の設計にしたことで、
APIの一時的な障害に影響されなくなりました。
おわりに
3つのChocolateyパッケージのCI/CDをAppVeyorからGitHub Actionsに移行し、 次の改善を実現しました。
- 毎日の定時実行で上流の新リリースを自動検知
- 審査中パッケージを含めたバージョン重複チェック
- GitHub Secretsによるセキュアなキー管理
- 強制実行モードによる緊急時対応
- ソースコードとCI/CDの一元管理
3つのリポジトリで共通のワークフローパターンを使っているため、 1つのリポジトリで改善した内容を他のリポジトリにも展開しやすい構成になっています。
参考サイト
- sacloudの各種パッケージ
- GitHub Actionsのドキュメント
- Chocolateyのドキュメント