feat: Cloudflare Workers を同期先プロバイダーとして追加 - #44
Conversation
Worker Secrets を REST API で同期する cloudflare provider を追加する。
- Sync: PUT /accounts/{id}/workers/scripts/{name}/secrets で登録し、
既存一覧の GET で新規/更新を分類。prune は DELETE で削除する
- secret: false(平文 vars)は警告のうえスキップする。平文 vars は
wrangler 設定の [vars] が所有しており、API で設定しても次の
wrangler deploy で上書きされて消えるため
- environments は別 Worker スクリプト <script>-<環境名> に解決する
(wrangler が [env.staging] を my-worker-staging としてデプロイする
慣習に合わせる)。config の cloudflare.environments で上書き可能
- スクリプト名の解決順: CLOUDFLARE_SCRIPT_NAME > config の
cloudflare.script > wrangler.jsonc/json/toml の name
- validate サブコマンド対応(GET のみの読み取り専用診断)
- setup サブコマンドに Cloudflare の対話プロンプトを追加
- モノレポ向けに cloudflare.scripts[] と --cloudflare-script を追加
- README(英日)・env-sync.yaml・docs/architecture.md・usage を更新
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThis change adds Cloudflare Workers as a synchronization provider, including configuration resolution, secret-only synchronization, pruning, validation, CLI integration, localization, setup support, tests, and English/Japanese documentation. ChangesCloudflare Workers support
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant CLI
participant CloudflareProvider
participant CloudflareAPI
CLI->>CloudflareProvider: Run sync or validate
CloudflareProvider->>CloudflareAPI: Resolve or inspect Worker secrets
CloudflareAPI-->>CloudflareProvider: Return names or HTTP status
CloudflareProvider->>CloudflareAPI: PUT, DELETE, or GET requests
CloudflareProvider-->>CLI: Report results and exit status
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Pull request overview
This PR adds Cloudflare Workers as a new sync destination provider for env-sync, integrating it into the existing provider registry/Provider interface and extending config, CLI flags, docs, i18n, and tests to support syncing Worker Secrets (secrets-only) with optional multi-script (monorepo) targeting.
Changes:
- Add a new
internal/provider/cloudflareprovider implementing sync/prune and a read-onlyvalidatesubcommand flow. - Extend config/CLI to support Cloudflare targets (including monorepo
cloudflare.scripts[]and--cloudflare-scriptfiltering) plus setup wizard prompts/output. - Update documentation, architecture notes, schema comments, and i18n catalogs; add extensive unit/integration tests for Cloudflare behavior.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Documents Cloudflare Workers provider usage, behavior (secrets-only), and configuration options. |
| README.ja.md | Japanese documentation updates for Cloudflare Workers provider usage and config. |
| internal/provider/provider.go | Adds CloudflareScript option for monorepo target filtering. |
| internal/provider/provider_test.go | Ensures Cloudflare provider is registered in the provider registry. |
| internal/provider/cloudflare/cloudflare.go | Implements Cloudflare Workers Secrets sync/prune logic, wrangler fallback, and response handling. |
| internal/provider/cloudflare/cloudflare_validate.go | Adds Cloudflare read-only validation (reachability/auth) via GET-only checks. |
| internal/provider/cloudflare/cloudflare_validate_test.go | Tests validate output, exit behavior, and GET-only enforcement. |
| internal/provider/cloudflare/cloudflare_test.go | Unit tests for pure helpers (name resolution, task expansion, parsing helpers, URL building). |
| internal/provider/cloudflare/cloudflare_integration_test.go | Integration tests with httptest verifying request paths/methods and dry-run/prune behavior. |
| internal/i18n/keys.go | Adds i18n message keys for Cloudflare config/setup/provider/validate flows. |
| internal/i18n/catalog_ja.go | Adds Japanese translations and usage text for Cloudflare features and flags. |
| internal/i18n/catalog_en.go | Adds English translations and usage text for Cloudflare features and flags. |
| internal/config/setup.go | Extends interactive setup to optionally collect Cloudflare config and emit YAML. |
| internal/config/setup_test.go | Adds tests for Cloudflare setup YAML generation and setup flow behaviors. |
| internal/config/config.go | Parses --cloudflare-script CLI flag into provider options. |
| internal/config/appconfig.go | Adds Cloudflare config schema, env/config resolution, and monorepo target resolution/validation. |
| internal/config/appconfig_test.go | Tests Cloudflare target resolution precedence, filtering, and validations. |
| env-sync.yaml | Updates schema comments and examples to include Cloudflare Workers semantics. |
| docs/architecture.md | Updates architecture documentation to include Cloudflare provider and dependency notes. |
| cmd/env-sync/main.go | Registers Cloudflare provider and updates command docs/comments for new provider. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // トークン未設定チェック(per-target) | ||
| // 単一ターゲット時は即エラー返却。複数ターゲット時は失敗として記録して残りを継続する。 | ||
| if !opts.DryRun && tgt.APIToken == "" { | ||
| if len(targets) == 1 { | ||
| return fmt.Errorf("%s", i18n.T(i18n.MsgCloudflareTokenMissingScript, tgt.Name)) | ||
| } | ||
| fmt.Fprint(os.Stderr, i18n.T(i18n.MsgCloudflareTokenSkipScript, tgt.Name)) | ||
| resolved = append(resolved, resolvedTarget{label: label, skipped: true}) | ||
| continue | ||
| } | ||
| // script / accountId は dry-run でも解決できていないと表示すべき対象が定まらないためエラーにする。 | ||
| if tgt.Script == "" { | ||
| return fmt.Errorf("%s", i18n.T(i18n.MsgCloudflareScriptMissing)) | ||
| } | ||
| if !opts.DryRun && tgt.AccountID == "" { | ||
| return fmt.Errorf("%s", i18n.T(i18n.MsgCloudflareAccountIDMissing)) | ||
| } | ||
|
|
||
| tasks := expandCloudflareTasks(secretEntries, tgt.Script, tgt.Environments) | ||
| scripts := taskScripts(tasks) |
| | `GITHUB_TOKEN` | Yes (GitHub) | GitHub access token (not required for dry-run) | | ||
| | `GITHUB_REPO` | – (GitHub) | `owner/repo` format. Auto-detected from config file or `git remote origin` if unset | | ||
| | `CLOUDFLARE_API_TOKEN` | Yes (Cloudflare) | API token with the Workers Scripts:Edit permission (not required for dry-run) | | ||
| | `CLOUDFLARE_ACCOUNT_ID` | Yes (Cloudflare) | Target account ID. Falls back to `cloudflare.account_id` in the config file | |
| | `GITHUB_TOKEN` | ◯(GitHub) | GitHub アクセストークン(dry-run 時は不要) | | ||
| | `GITHUB_REPO` | –(GitHub) | `owner/repo` 形式。未指定なら config ファイルまたは `git remote origin` から自動取得 | | ||
| | `CLOUDFLARE_API_TOKEN` | ◯(Cloudflare) | Workers Scripts:Edit 権限を持つ API トークン(dry-run 時は不要) | | ||
| | `CLOUDFLARE_ACCOUNT_ID` | ◯(Cloudflare) | 対象アカウント ID。未指定なら config ファイルの `cloudflare.account_id` | |
複数ターゲットのうち 1 つが API トークン未設定でスキップされた場合、 tasks を持たない resolvedTarget を積んでいたため送信フェーズの totalNG に 0 が加算され、ターゲットを丸ごと同期できなかったのに 他ターゲットが成功すれば exit 0 になっていた。 スキップ時にも tasks を展開して件数を保持し、失敗として集計する。 syncOsExit を差し替え可能にして、この挙動を検証するテストを追加した (修正前のコードでは exit 0 となりテストが落ちることを確認済み)。 あわせて PR レビューの指摘に対応: - provider_test.go: cloudflare も検証するようになったテスト名を TestRegistry_ProvidersRegistered へリネーム - README(英日): CLOUDFLARE_ACCOUNT_ID は dry-run 時には必須でない (ただし新規/更新の判定は表示されない)ことを明記 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
internal/provider/cloudflare/cloudflare.go (1)
399-434: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winNo
context.Contextis plumbed through the Cloudflare HTTP helpers, so every request useshttp.NewRequestand trips the enablednoctxlinter. Requests are bounded only byclient.Timeoutand cannot be cancelled.
internal/provider/cloudflare/cloudflare.go#L399-L434: accept actxinsyncOneTargetanddeleteSecretsand switch Lines 403 and 434 tohttp.NewRequestWithContext; do the same forfetchSecretNamesForScriptat Line 477 and pass the context down fromSync.internal/provider/cloudflare/cloudflare_validate.go#L128-L133: accept actxincheckAccess, usehttp.NewRequestWithContextat Line 129, and pass it fromValidate.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/provider/cloudflare/cloudflare.go` around lines 399 - 434, Add context.Context parameters to syncOneTarget, deleteSecrets, fetchSecretNamesForScript, and checkAccess; pass the context from Sync and Validate through all callers. Replace each helper’s http.NewRequest call with http.NewRequestWithContext using that context, including the request creation sites in cloudflare.go and cloudflare_validate.go.Source: Linters/SAST tools
internal/provider/cloudflare/cloudflare_integration_test.go (1)
349-367: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueGuard
syncOsExitin everySynctest.Tests that don't call
captureSyncOsExit(this one,TestSync_PrunesUndefinedSecrets,TestSync_UsesWranglerFallbackForScriptName) will invoke the realos.Exit(1)and abort the whole test binary if any request unexpectedly fails, masking the actual assertion. AddingcaptureSyncOsExit(t)makes the failure observable.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/provider/cloudflare/cloudflare_integration_test.go` around lines 349 - 367, In every affected Sync test—TestSync_SkipsPlainVarsAndSyncsSecrets, TestSync_PrunesUndefinedSecrets, and TestSync_UsesWranglerFallbackForScriptName—call captureSyncOsExit(t) before invoking cloudflareProvider.Sync so unexpected request failures are intercepted instead of terminating the test process.internal/i18n/catalog_ja.go (1)
361-362: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDrop the dead Cloudflare URL-build i18n keys.
secretsURLhas no failure path, andMsgCloudflareURLBuildFailOut/MsgCloudflareURLBuildFailInternalhave no call sites; remove these entries from both catalogs andinternal/i18n/keys.go.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/i18n/catalog_ja.go` around lines 361 - 362, Remove the unused MsgCloudflareURLBuildFailOut and MsgCloudflareURLBuildFailInternal definitions from both translation catalogs and from the key declarations in internal/i18n/keys.go, leaving all remaining Cloudflare messages unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/i18n/catalog_en.go`:
- Around line 228-233: Update the first line of MsgInitYAMLHeader to list GCP
and Cloudflare Workers alongside the existing platforms, matching the Japanese
catalog wording while preserving the remaining YAML documentation.
In `@internal/provider/cloudflare/cloudflare_validate_test.go`:
- Around line 203-206: Fix the token leak assertion in the validation test by
using a distinctive token value that cannot match the printed `token` label,
then assert that this exact value is absent from `got` while retaining the
expected `[set]` check. Update the existing test logic around the
strings.Contains calls without changing Validate behavior.
---
Nitpick comments:
In `@internal/i18n/catalog_ja.go`:
- Around line 361-362: Remove the unused MsgCloudflareURLBuildFailOut and
MsgCloudflareURLBuildFailInternal definitions from both translation catalogs and
from the key declarations in internal/i18n/keys.go, leaving all remaining
Cloudflare messages unchanged.
In `@internal/provider/cloudflare/cloudflare_integration_test.go`:
- Around line 349-367: In every affected Sync
test—TestSync_SkipsPlainVarsAndSyncsSecrets, TestSync_PrunesUndefinedSecrets,
and TestSync_UsesWranglerFallbackForScriptName—call captureSyncOsExit(t) before
invoking cloudflareProvider.Sync so unexpected request failures are intercepted
instead of terminating the test process.
In `@internal/provider/cloudflare/cloudflare.go`:
- Around line 399-434: Add context.Context parameters to syncOneTarget,
deleteSecrets, fetchSecretNamesForScript, and checkAccess; pass the context from
Sync and Validate through all callers. Replace each helper’s http.NewRequest
call with http.NewRequestWithContext using that context, including the request
creation sites in cloudflare.go and cloudflare_validate.go.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: c7e77f32-9297-4437-bf5b-069242c44fa2
📒 Files selected for processing (20)
README.ja.mdREADME.mdcmd/env-sync/main.godocs/architecture.mdenv-sync.yamlinternal/config/appconfig.gointernal/config/appconfig_test.gointernal/config/config.gointernal/config/setup.gointernal/config/setup_test.gointernal/i18n/catalog_en.gointernal/i18n/catalog_ja.gointernal/i18n/keys.gointernal/provider/cloudflare/cloudflare.gointernal/provider/cloudflare/cloudflare_integration_test.gointernal/provider/cloudflare/cloudflare_test.gointernal/provider/cloudflare/cloudflare_validate.gointernal/provider/cloudflare/cloudflare_validate_test.gointernal/provider/provider.gointernal/provider/provider_test.go
PR レビュー(CodeRabbit)の指摘に対応する。 - catalog_en.go: MsgInitYAMLHeader の 1 行目が "Vercel / GitHub Actions" のままで、本文や日本語カタログと食い違っていたため GCP / Cloudflare Workers を追記して英日を揃えた - cloudflare_validate_test.go: トークン漏洩の検証が機能していなかった。 値 "tok" が出力ラベル "token" の部分文字列であるため第 1 条件が常に真、 "[set]" は必ず出力されるため第 2 条件が常に偽となり、AND が恒偽で アサーションが一度も発火しなかった。衝突しないトークン値に変え、 「値が出力されないこと」と「マスクラベルが出ること」を個別に検証する (意図的にトークンを出力させるとテストが落ちることを確認済み) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
概要
env-syncの同期先に Cloudflare Workers を追加しました。既存の provider 抽象(registry +Providerインターフェース)にそのまま乗る形でinternal/provider/cloudflare/を新規追加しています。主な設計判断
1. Worker Secrets のみ同期し、平文 vars はスキップする
Cloudflare Workers の環境変数は「Secrets」と「平文 vars」の 2 種類ありますが、本 provider は Secrets のみを同期します。
平文 vars は wrangler 設定の
[vars]セクションが所有しており、API やダッシュボードで設定した値は次のwrangler deployで上書きされて消えます。同期しても黙って失われるため、secret: falseのエントリは理由を添えて警告・スキップします(Secrets はデプロイをまたいで保持されます)。2.
environmentsは別の Worker スクリプトに対応づけるwrangler は
[env.staging]を<script>-stagingという別の Worker としてデプロイします。この慣習に合わせ、宣言した環境ごとに対応するスクリプトへ書き込みます。environments省略my-workerenvironments: [staging]my-worker-stagingenvironments: [production, staging]my-worker-productionとmy-worker-staging本番 Worker がベーススクリプトの場合は、認証 config の
cloudflare.environmentsでマッピングを上書きできます。3. スクリプト名は wrangler 設定からフォールバック解決する
CLOUDFLARE_SCRIPT_NAME→ config のcloudflare.script→wrangler.jsonc/wrangler.json/wrangler.tomlのnameの順で解決します。Vercel の.vercel/project.jsonフォールバックと対称的な設計です。JSONC のコメント除去と TOML のトップレベル
name抽出は依存を増やさない簡易実装で、文字列リテラル内やテーブルヘッダ以降を拾わないことをテストで担保しています。対応範囲
Sync(新規/更新の分類・確認プロンプト・dry-run)prune(対象スクリプトの Worker Secrets のみ削除)validateサブコマンド(GET のみの読み取り専用診断、401/403/404 の推定原因表示)setupサブコマンドの対話プロンプトcloudflare.scripts[]+--cloudflare-script)env-sync.yaml・docs/architecture.md・usage)テスト
gofmt/go vet/go test -race ./...すべて通過。544 件(従来 449 → 95 件追加)。success:falseの扱い、prune、dry-run が書き込まないことResolveCloudflareTargetsの解決優先順位とバリデーション実バイナリでの手動確認も実施済み(wrangler.toml からの名前解決、環境ごとの展開、config によるマッピング上書き、
validateの各出力)。補足
--dry-runでも既存シークレット取得のため GET を 1 回投げます(新規/更新の分類を表示するため)。Vercel provider と同じ挙動で、書き込みは行いません。🤖 Generated with Claude Code
Summary by CodeRabbit
secret: true;secret: falseis warned and skipped).--cloudflare-script.validate --provider cloudflarefor read-only connectivity and configuration diagnostics.