fix(rde): fix upgrade reclone 404 + implement image strategy with blueprint sync#638
Merged
Conversation
…eprint sync Fixes two bugs in 'qovery rde upgrade': 1. Reclone strategy: was calling CloneEnvironment with the blueprint's project ID instead of its environment ID, causing 404 errors. Now resolves the blueprint env ID first via rdeFindBlueprintEnv. 2. Image strategy: was only redeploying without syncing service configs from the blueprint. Now reads all services (containers, applications, jobs, helms) from the blueprint environment, matches them by name to the child's services, and updates image/source configuration using the Edit APIs before deploying. Additional improvements: - Bulk reclone now runs in 4 parallel phases (delete all -> wait -> clone all -> deploy all) instead of sequentially per RDE - Owner email and cluster ID are preserved across reclone - Adds --sync-all/--sync-resources/--sync-ports/--sync-healthchecks/ --sync-storage flags to control what additional config gets synced from the blueprint (source/image is always synced) - Adds rdeWaitForEnvsDeletion helper that polls until environments return 404 or timeout
benjaminch
approved these changes
May 10, 2026
benjaminch
approved these changes
May 10, 2026
Contributor
There was a problem hiding this comment.
Pull request overview
This PR updates the RDE upgrade workflow to fix blueprint reclone failures and to make the image upgrade strategy actually sync service configuration from the blueprint prior to deployment. It also restructures bulk reclone upgrades into phased execution (delete → wait → clone → deploy) to reduce total time when upgrading multiple RDEs.
Changes:
- Fix
reclonecloning by resolving and using the blueprint environment ID (instead of blueprint project ID) before callingCloneEnvironment. - Implement
--strategy imageas “sync from blueprint then deploy”, with additional--sync-*scope flags. - Rework bulk
recloneinto 4 phases (batch delete, wait for deletion, clone, deploy).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 10 comments.
| File | Description |
|---|---|
cmd/rde.go |
Adds helpers for determining blueprint cluster ID and polling for environment deletion. |
cmd/rde_upgrade.go |
Refactors upgrade logic to split image vs reclone paths and introduces phased bulk reclone flow plus new sync flags. |
cmd/rde_sync.go |
New service sync helpers to copy source/image (and optionally other config) from blueprint services into child services before deploying. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+545
to
+555
| // rdeIsEnvDeleted checks if an environment no longer exists (404) or is in a terminal deleted state. | ||
| func rdeIsEnvDeleted(client *qovery.APIClient, envId string) bool { | ||
| _, resp, err := client.EnvironmentMainCallsAPI.GetEnvironmentStatuses(ctx(), envId).Execute() | ||
| if err != nil { | ||
| // If 404, it's deleted | ||
| if resp != nil && resp.StatusCode == 404 { | ||
| return true | ||
| } | ||
| return false | ||
| } | ||
| return false |
Comment on lines
18
to
+26
| Long: `Upgrade one or all RDE environments using one of two strategies: | ||
|
|
||
| image (default) - Redeploy the environment (re-pulls latest images) | ||
| reclone - Delete the environment, re-clone from blueprint, and deploy | ||
| WARNING: uncommitted changes will be lost with reclone | ||
|
|
||
| If --name is provided, upgrades a single RDE. Otherwise, upgrades all RDEs.`, | ||
| If --name is provided, upgrades a single RDE. Otherwise, upgrades all RDEs. | ||
| When upgrading multiple RDEs with reclone, environments are deleted in parallel | ||
| for faster execution.`, |
Comment on lines
+182
to
+199
| // Pre-resolve all blueprint env IDs and cluster IDs (grouped by blueprint project ID) | ||
| type blueprintRef struct { | ||
| envId string | ||
| clusterId string | ||
| } | ||
| bpRefMap := make(map[string]*blueprintRef) | ||
| for _, child := range children { | ||
| if _, ok := bpRefMap[child.BlueprintProjectId]; !ok { | ||
| bpEnvInfo, err := rdeFindBlueprintEnv(client, child.BlueprintProjectId) | ||
| if err == nil && bpEnvInfo != nil { | ||
| clusterId := rdeGetBlueprintClusterId(client, bpEnvInfo.EnvId) | ||
| bpRefMap[child.BlueprintProjectId] = &blueprintRef{ | ||
| envId: bpEnvInfo.EnvId, | ||
| clusterId: clusterId, | ||
| } | ||
| } | ||
| } | ||
| } |
Comment on lines
+56
to
+62
| srcStorage := child.Storage | ||
| if rdeSyncAll || rdeSyncStorage { | ||
| srcStorage = bp.Storage | ||
| } | ||
| for _, s := range srcStorage { | ||
| storage = append(storage, qovery.ServiceStorageRequestStorageInner{ | ||
| Id: &s.Id, |
Comment on lines
+173
to
+178
| if rdeSyncAll || rdeSyncStorage { | ||
| srcStorage = bp.Storage | ||
| } | ||
| for _, s := range srcStorage { | ||
| storage = append(storage, qovery.ServiceStorageRequestStorageInner{ | ||
| Id: &s.Id, |
Comment on lines
+164
to
+167
| Url: bp.GitRepository.Url, | ||
| Branch: bp.GitRepository.Branch, | ||
| RootPath: bp.GitRepository.RootPath, | ||
| Provider: bp.GitRepository.Provider, |
Comment on lines
+348
to
+351
| Url: docker.GitRepository.Url, | ||
| Branch: docker.GitRepository.Branch, | ||
| RootPath: docker.GitRepository.RootPath, | ||
| Provider: docker.GitRepository.Provider, |
Comment on lines
+500
to
+502
| Url: gitSrc.GitRepository.Url, | ||
| Branch: gitSrc.GitRepository.Branch, | ||
| RootPath: gitSrc.GitRepository.RootPath, |
Comment on lines
+528
to
+534
| // rdeConvertHelmValuesOverride converts HelmResponseAllOfValuesOverride to HelmRequestAllOfValuesOverride. | ||
| func rdeConvertHelmValuesOverride(v *qovery.HelmResponseAllOfValuesOverride) *qovery.HelmRequestAllOfValuesOverride { | ||
| return &qovery.HelmRequestAllOfValuesOverride{ | ||
| Set: v.Set, | ||
| SetString: v.SetString, | ||
| SetJson: v.SetJson, | ||
| } |
Comment on lines
+471
to
+479
| req := qovery.HelmRequest{ | ||
| Name: child.Name, | ||
| Description: child.Description, | ||
| TimeoutSec: child.TimeoutSec, | ||
| AutoDeploy: child.AutoDeploy, | ||
| Source: *bpSource, // always sync source | ||
| Arguments: child.Arguments, | ||
| AllowClusterWideResources: &child.AllowClusterWideResources, | ||
| ValuesOverride: *childValues, |
erebe
approved these changes
May 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
qovery rde upgrade --strategy reclone404 error caused by passing a project ID toCloneEnvironmentinstead of the blueprint's environment ID--strategy imagethat syncs service source/image configurations from the blueprint before deploying, instead of just redeploying with stale configsBug Fixes
Reclone 404 (Critical)
Before:
CloneEnvironment(ctx, child.BlueprintProjectId)— passes project ID → 404After: Resolves blueprint env ID via
rdeFindBlueprintEnv()first, thenCloneEnvironment(ctx, blueprintEnvId)Image strategy not syncing
Before: Just called
DeployEnvironment()— re-pulls same images, ignores blueprint changesAfter: For each service type (containers, apps, jobs, helms):
New Features
Sync scope flags (
--strategy image)Source/image config is always synced. Additional flags control what else gets synced:
--sync-resources--sync-ports--sync-healthchecks--sync-storage--sync-allParallel bulk reclone
When upgrading multiple RDEs with
--strategy reclone:Metadata preservation on reclone
RDE_OWNER_EMAIL) is read before delete and re-set after cloneFiles Changed (3)
cmd/rde_sync.gocmd/rde_upgrade.gocmd/rde.gordeGetBlueprintClusterId,rdeIsEnvDeleted,rdeWaitForEnvsDeletionTesting
Tested live against Qovery API (
builder-workspacesblueprint inQovery Realm):