Skip to content

fix(rde): fix upgrade reclone 404 + implement image strategy with blueprint sync#638

Merged
evoxmusic merged 1 commit into
mainfrom
fix/rde-upgrade-reclone
May 10, 2026
Merged

fix(rde): fix upgrade reclone 404 + implement image strategy with blueprint sync#638
evoxmusic merged 1 commit into
mainfrom
fix/rde-upgrade-reclone

Conversation

@evoxmusic
Copy link
Copy Markdown
Contributor

Summary

  • Fix qovery rde upgrade --strategy reclone 404 error caused by passing a project ID to CloneEnvironment instead of the blueprint's environment ID
  • Implement proper --strategy image that syncs service source/image configurations from the blueprint before deploying, instead of just redeploying with stale configs
  • Add parallel execution for bulk reclone upgrades (4 phases: delete all → wait → clone all → deploy all)

Bug Fixes

Reclone 404 (Critical)

Before: CloneEnvironment(ctx, child.BlueprintProjectId) — passes project ID → 404
After: Resolves blueprint env ID via rdeFindBlueprintEnv() first, then CloneEnvironment(ctx, blueprintEnvId)

Image strategy not syncing

Before: Just called DeployEnvironment() — re-pulls same images, ignores blueprint changes
After: For each service type (containers, apps, jobs, helms):

  1. Lists services in blueprint and child environments
  2. Matches by name
  3. Updates child's source/image config from blueprint via Edit APIs (read-modify-write pattern)
  4. Deploys

New Features

Sync scope flags (--strategy image)

Source/image config is always synced. Additional flags control what else gets synced:

Flag Syncs
--sync-resources CPU, memory, min/max instances, autoscaling
--sync-ports Port configuration
--sync-healthchecks Health check configuration
--sync-storage Storage volumes
--sync-all All of the above

Parallel bulk reclone

When upgrading multiple RDEs with --strategy reclone:

  • Phase 1: Fire all delete requests in parallel
  • Phase 2: Poll until all environments return 404 (timeout: 180s)
  • Phase 3: Clone all from blueprint
  • Phase 4: Deploy all

Metadata preservation on reclone

  • Owner email (RDE_OWNER_EMAIL) is read before delete and re-set after clone
  • Cluster ID defaults to blueprint's cluster

Files Changed (3)

File Lines Description
cmd/rde_sync.go +535 New file: sync helpers for containers, applications, jobs, helms
cmd/rde_upgrade.go +196/-51 Rewritten image/reclone strategies, parallel bulk upgrade
cmd/rde.go +46 New helpers: rdeGetBlueprintClusterId, rdeIsEnvDeleted, rdeWaitForEnvsDeletion

Testing

Tested live against Qovery API (builder-workspaces blueprint in Qovery Realm):

# Image strategy — syncs 2 services from blueprint
$ qovery rde upgrade --strategy image --name img-test
  Upgrading img-test (strategy: image - sync from blueprint and deploy)...
    Synced application: workspace (branch: main)
    Synced job: ttl-auto-shutdown
    Synced 2 service(s) from blueprint.
    Request to deploy img-test has been queued..

# Reclone strategy — parallel 4-phase upgrade
$ qovery rde upgrade --strategy reclone
  Phase 1/4: Deleting old environments...
    Delete requested: upgrade-1
    Delete requested: upgrade-2
  Phase 2/4: Waiting for deletions to complete...
    Deletions complete.
  Phase 3/4: Cloning from blueprint...
    Cloned: upgrade-1 (env: 49742a0d-...)
    Cloned: upgrade-2 (env: cfaee1bb-...)
  Phase 4/4: Deploying...
    Request to deploy upgrade-1 has been queued..
    Request to deploy upgrade-2 has been queued..

…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
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 reclone cloning by resolving and using the blueprint environment ID (instead of blueprint project ID) before calling CloneEnvironment.
  • Implement --strategy image as “sync from blueprint then deploy”, with additional --sync-* scope flags.
  • Rework bulk reclone into 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 thread cmd/rde.go
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 thread cmd/rde_upgrade.go
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 thread cmd/rde_upgrade.go
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 thread cmd/rde_sync.go
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 thread cmd/rde_sync.go
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 thread cmd/rde_sync.go
Comment on lines +164 to +167
Url: bp.GitRepository.Url,
Branch: bp.GitRepository.Branch,
RootPath: bp.GitRepository.RootPath,
Provider: bp.GitRepository.Provider,
Comment thread cmd/rde_sync.go
Comment on lines +348 to +351
Url: docker.GitRepository.Url,
Branch: docker.GitRepository.Branch,
RootPath: docker.GitRepository.RootPath,
Provider: docker.GitRepository.Provider,
Comment thread cmd/rde_sync.go
Comment on lines +500 to +502
Url: gitSrc.GitRepository.Url,
Branch: gitSrc.GitRepository.Branch,
RootPath: gitSrc.GitRepository.RootPath,
Comment thread cmd/rde_sync.go
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 thread cmd/rde_sync.go
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,
@evoxmusic evoxmusic merged commit f23a824 into main May 10, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants