Skip to content

Commit ea2ab2d

Browse files
authored
Merge 334357a into c12bb49
2 parents c12bb49 + 334357a commit ea2ab2d

File tree

5 files changed

+68
-126
lines changed

5 files changed

+68
-126
lines changed

internal/command/deploy/machines_launchinput.go

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package deploy
22

33
import (
44
"fmt"
5+
"slices"
56
"strconv"
67
"strings"
78

@@ -297,18 +298,14 @@ func skipLaunch(origMachineRaw *fly.Machine, mConfig *fly.MachineConfig) bool {
297298
}
298299

299300
switch {
300-
case state == fly.MachineStateStarted:
301+
case slices.Contains([]string{fly.MachineStateStarted, "starting", "failed"}, state):
301302
return false
302303
case len(mConfig.Standbys) > 0:
303304
return true
304-
case state == fly.MachineStateStopped, state == fly.MachineStateSuspended:
305-
for _, s := range mConfig.Services {
306-
if (s.Autostop != nil && *s.Autostop != fly.MachineAutostopOff) || (s.Autostart != nil && *s.Autostart) {
307-
return true
308-
}
309-
}
305+
case origMachineRaw == nil:
306+
return false
310307
}
311-
return false
308+
return true
312309
}
313310

314311
// updateContainerImage sets container.Image = mConfig.Image in any container where image == "."

test/preflight/apps_v2_integration_test.go

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package preflight
55

66
import (
7+
"encoding/json"
78
"errors"
89
"fmt"
910
"net/http"
@@ -84,7 +85,7 @@ func TestAppsV2Example(t *testing.T) {
8485
ENV BUILT_BY_DOCKERFILE=true
8586
`
8687
dockerfilePath := filepath.Join(f.WorkDir(), "Dockerfile")
87-
err := os.WriteFile(dockerfilePath, []byte(dockerfileContent), 0644)
88+
err := os.WriteFile(dockerfilePath, []byte(dockerfileContent), 0o644)
8889
if err != nil {
8990
f.Fatalf("failed to write dockerfile at %s error: %v", dockerfilePath, err)
9091
}
@@ -109,7 +110,7 @@ func TestAppsV2ConfigChanges(t *testing.T) {
109110
newConfigFile := strings.Replace(string(configFileBytes), `FOO = 'BAR'`, `BAR = "QUX"`, 1)
110111
require.Contains(f, newConfigFile, `BAR = "QUX"`)
111112

112-
err = os.WriteFile(configFilePath, []byte(newConfigFile), 0666)
113+
err = os.WriteFile(configFilePath, []byte(newConfigFile), 0o666)
113114
require.NoError(t, err)
114115

115116
f.Fly("deploy --buildkit --remote-only --detach")
@@ -177,7 +178,7 @@ func TestAppsV2Config_ParseExperimental(t *testing.T) {
177178
auto_rollback = true
178179
`
179180

180-
err := os.WriteFile(configFilePath, []byte(config), 0644)
181+
err := os.WriteFile(configFilePath, []byte(config), 0o644)
181182
require.NoError(t, err, "error trying to write %s", configFilePath)
182183

183184
result := f.Fly("launch --no-deploy --ha=false --name %s --region ord --copy-config --org %s", appName, f.OrgSlug())
@@ -208,7 +209,7 @@ func TestAppsV2Config_ProcessGroups(t *testing.T) {
208209

209210
deployToml := func(toml string) *testlib.FlyctlResult {
210211
toml = "app = \"" + appName + "\"\n" + toml
211-
err := os.WriteFile(configFilePath, []byte(toml), 0666)
212+
err := os.WriteFile(configFilePath, []byte(toml), 0o666)
212213
require.NoError(t, err, "error trying to write %s", configFilePath)
213214
cmd := f.Fly("deploy --buildkit --remote-only --detach --now --image nginx --ha=false")
214215
cmd.AssertSuccessfulExit()
@@ -252,10 +253,8 @@ func TestAppsV2Config_ProcessGroups(t *testing.T) {
252253

253254
deployOut := deployToml(`
254255
[[services]]
255-
http_checks = []
256256
internal_port = 8080
257257
protocol = "tcp"
258-
script_checks = []
259258
260259
[[services.ports]]
261260
port = 80
@@ -279,10 +278,8 @@ bar_web = "bash -c 'while true; do sleep 10; done'"
279278
280279
[[services]]
281280
processes = ["web"] # this service only applies to the web process
282-
http_checks = []
283281
internal_port = 8080
284282
protocol = "tcp"
285-
script_checks = []
286283
287284
[[services.ports]]
288285
port = 80
@@ -320,6 +317,7 @@ bar_web = "bash -c 'while true; do sleep 10; done'"
320317
if len(f.OtherRegions()) > 0 {
321318
secondaryRegion = f.OtherRegions()[0]
322319
}
320+
323321
f.Fly("m clone %s --region %s", barWebMachId, secondaryRegion)
324322
f.Fly("machine update %s -m ABCD=EFGH -y", webMachId).AssertSuccessfulExit()
325323

@@ -347,6 +345,11 @@ web = "nginx -g 'daemon off;'"
347345
"web": 1,
348346
})
349347

348+
// json dump `machines` variable as well, to verify that the machine IDs are what we expect
349+
machinesJson, _ := json.MarshalIndent(machines, "", " ")
350+
f.Logf("machines variable dump: %s", string(machinesJson))
351+
f.Logf("machines before clone: %s", f.Fly("machines list -a %s --json", appName).StdOutString())
352+
350353
// Step 5: Set secrets, to ensure that machine data is kept during a 'restartOnly' deploy.
351354
f.Fly("machine update %s -m CUSTOM=META -y", webMachId).AssertSuccessfulExit()
352355
f.Fly("secrets set 'SOME=MY_SECRET_TEST_STRING' -a %s", appName).AssertSuccessfulExit()
@@ -512,7 +515,7 @@ func TestImageLabel(t *testing.T) {
512515
ENV BUILT_BY_DOCKERFILE=true
513516
`
514517
dockerfilePath := filepath.Join(f.WorkDir(), "Dockerfile")
515-
err := os.WriteFile(dockerfilePath, []byte(dockerfileContent), 0644)
518+
err := os.WriteFile(dockerfilePath, []byte(dockerfileContent), 0o644)
516519
if err != nil {
517520
f.Fatalf("failed to write dockerfile at %s error: %v", dockerfilePath, err)
518521
}

test/preflight/fly_deploy_test.go

Lines changed: 7 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818
"github.com/stretchr/testify/assert"
1919
"github.com/stretchr/testify/require"
2020

21-
//fly "github.com/superfly/fly-go"
21+
// fly "github.com/superfly/fly-go"
2222

2323
"github.com/superfly/flyctl/test/preflight/testlib"
2424
)
@@ -84,36 +84,11 @@ func TestFlyDeployHAPlacement(t *testing.T) {
8484
f := testlib.NewTestEnvFromEnv(t)
8585
appName := f.CreateRandomAppName()
8686

87-
// Create the app without deploying to avoid the Corrosion replication race
8887
f.Fly(
89-
"launch --org %s --name %s --region %s --image nginx --internal-port 80 --no-deploy",
88+
"launch --org %s --name %s --region %s --image nginx --internal-port 80 --ha",
9089
f.OrgSlug(), appName, f.PrimaryRegion(),
9190
)
9291

93-
// Retry the deploy command to handle Corrosion replication lag race conditions
94-
// The backend may not have replicated the app record to all hosts yet when
95-
// creating the second machine for HA, resulting in "sql: no rows in result set" errors
96-
var lastError string
97-
require.EventuallyWithT(t, func(c *assert.CollectT) {
98-
result := f.FlyAllowExitFailure("deploy --buildkit --remote-only")
99-
if result.ExitCode() != 0 {
100-
stderr := result.StdErrString()
101-
lastError = stderr
102-
// Only retry if it's the known Corrosion replication lag error
103-
if strings.Contains(stderr, "failed to get app: sql: no rows in result set") {
104-
t.Logf("Corrosion replication lag detected, retrying... (error: %s)", stderr)
105-
assert.Fail(c, "Corrosion replication lag, retrying...")
106-
} else {
107-
// Log the unexpected error and fail without retrying
108-
t.Logf("Deploy failed with unexpected error (will not retry): %s", stderr)
109-
assert.Fail(c, fmt.Sprintf("deploy failed with unexpected error: %s", stderr))
110-
}
111-
} else {
112-
// Explicitly assert success so EventuallyWithT knows we passed
113-
assert.True(c, true, "deploy succeeded")
114-
}
115-
}, 30*time.Second, 5*time.Second, "deploy should succeed after Corrosion replication, last error: %s", lastError)
116-
11792
assertHostDistribution(t, f, appName, 2)
11893
}
11994

@@ -241,35 +216,10 @@ func testDeployNodeAppWithRemoteBuilder(tt *testing.T) {
241216
require.NoError(t, err)
242217

243218
t.Logf("deploy %s", appName)
244-
// Retry deploy to handle transient network errors (DNS, WireGuard, buildkit connection issues)
245-
// BuildKit deployments can fail with various transient errors during the initial connection
246-
var lastError string
247-
require.EventuallyWithT(tt, func(c *assert.CollectT) {
248-
result := f.FlyAllowExitFailure("deploy --buildkit --remote-only --ha=false")
249-
if result.ExitCode() != 0 {
250-
stderr := result.StdErrString()
251-
lastError = stderr
252-
t.Logf("Deploy failed (will retry), error: %s", stderr)
253-
assert.Fail(c, "deploy failed, retrying...")
254-
} else {
255-
assert.True(c, true, "deploy succeeded")
256-
}
257-
}, 120*time.Second, 10*time.Second, "deploy should succeed after retries, last error: %s", lastError)
219+
f.Fly("deploy --remote-only --ha=false")
258220

259221
t.Logf("deploy %s again", appName)
260-
// Retry second deploy as well
261-
lastError = ""
262-
require.EventuallyWithT(tt, func(c *assert.CollectT) {
263-
result := f.FlyAllowExitFailure("deploy --buildkit --remote-only --strategy immediate --ha=false")
264-
if result.ExitCode() != 0 {
265-
stderr := result.StdErrString()
266-
lastError = stderr
267-
t.Logf("Deploy failed (will retry), error: %s", stderr)
268-
assert.Fail(c, "deploy failed, retrying...")
269-
} else {
270-
assert.True(c, true, "deploy succeeded")
271-
}
272-
}, 120*time.Second, 10*time.Second, "deploy should succeed after retries, last error: %s", lastError)
222+
f.Fly("deploy --remote-only --strategy immediate")
273223

274224
body, err := testlib.RunHealthCheck(fmt.Sprintf("https://%s.fly.dev", appName))
275225
require.NoError(t, err)
@@ -298,35 +248,10 @@ func testDeployNodeAppWithBuildKitRemoteBuilder(tt *testing.T) {
298248
require.NoError(t, err)
299249

300250
t.Logf("deploy %s with BuildKit", appName)
301-
// Retry deploy to handle transient network errors (DNS, WireGuard, buildkit connection issues)
302-
// BuildKit deployments can fail with various transient errors during the initial connection
303-
var lastError string
304-
require.EventuallyWithT(tt, func(c *assert.CollectT) {
305-
result := f.FlyAllowExitFailure("deploy --buildkit --remote-only --ha=false")
306-
if result.ExitCode() != 0 {
307-
stderr := result.StdErrString()
308-
lastError = stderr
309-
t.Logf("Deploy failed (will retry), error: %s", stderr)
310-
assert.Fail(c, "deploy failed, retrying...")
311-
} else {
312-
assert.True(c, true, "deploy succeeded")
313-
}
314-
}, 120*time.Second, 10*time.Second, "deploy should succeed after retries, last error: %s", lastError)
251+
f.Fly("deploy --buildkit --remote-only --ha=false")
315252

316253
t.Logf("deploy %s again with BuildKit", appName)
317-
// Retry second deploy as well
318-
lastError = ""
319-
require.EventuallyWithT(tt, func(c *assert.CollectT) {
320-
result := f.FlyAllowExitFailure("deploy --buildkit --remote-only --strategy immediate --ha=false")
321-
if result.ExitCode() != 0 {
322-
stderr := result.StdErrString()
323-
lastError = stderr
324-
t.Logf("Deploy failed (will retry), error: %s", stderr)
325-
assert.Fail(c, "deploy failed, retrying...")
326-
} else {
327-
assert.True(c, true, "deploy succeeded")
328-
}
329-
}, 120*time.Second, 10*time.Second, "deploy should succeed after retries, last error: %s", lastError)
254+
f.Fly("deploy --buildkit --remote-only --strategy immediate")
330255

331256
body, err := testlib.RunHealthCheck(fmt.Sprintf("https://%s.fly.dev", appName))
332257
require.NoError(t, err)
@@ -461,7 +386,7 @@ func TestDeployManifest(t *testing.T) {
461386
appName := f.CreateRandomAppName()
462387
f.Fly("launch --org %s --name %s --region %s --image nginx:latest --internal-port 80 --ha=false --strategy rolling", f.OrgSlug(), appName, f.PrimaryRegion())
463388

464-
var manifestPath = filepath.Join(f.WorkDir(), "manifest.json")
389+
manifestPath := filepath.Join(f.WorkDir(), "manifest.json")
465390

466391
f.Fly("deploy --buildkit --remote-only --export-manifest %s", manifestPath)
467392

test/preflight/fly_machine_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ func TestFlyMachineRun_standbyFor(t *testing.T) {
108108
s1 = findMachineByID(ml, s1.ID)
109109
require.Equal(f, 2, len(ml))
110110
// Updating a stopped machine doesn't start it
111-
require.Equal(f, "started", s1.State)
111+
require.Equal(f, "stopped", s1.State)
112112
require.Empty(f, s1.Config.Standbys)
113113

114114
// Clone and set its standby to the source

test/preflight/testlib/helpers.go

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"runtime"
2020
"sort"
2121
"strings"
22+
"sync"
2223
"testing"
2324
"time"
2425

@@ -30,6 +31,16 @@ import (
3031

3132
const defaultRegion = "cdg"
3233

34+
type selectedRegions struct {
35+
primary string
36+
other []string
37+
}
38+
39+
var (
40+
regionSelectionOnce sync.Once
41+
regionSelection selectedRegions
42+
)
43+
3344
type platformRegion struct {
3445
Code string `json:"code"`
3546
Name string `json:"name"`
@@ -92,37 +103,43 @@ func getBestRegions(count int) ([]string, error) {
92103
}
93104

94105
func primaryRegionFromEnv() string {
95-
regions := os.Getenv("FLY_PREFLIGHT_TEST_FLY_REGIONS")
96-
if regions == "" {
97-
// Try to dynamically select best region
98-
best, err := getBestRegions(1)
99-
if err == nil && len(best) > 0 {
100-
terminal.Warnf("no region set with FLY_PREFLIGHT_TEST_FLY_REGIONS, auto-selected region with best capacity: %s", best[0])
101-
return best[0]
102-
}
103-
// Fall back to hardcoded default
104-
terminal.Warnf("no region set with FLY_PREFLIGHT_TEST_FLY_REGIONS, failed to auto-select (%v), using fallback: %s", err, defaultRegion)
105-
return defaultRegion
106-
}
107-
pieces := strings.SplitN(regions, " ", 2)
108-
return pieces[0]
106+
regions := selectRegionsFromEnv()
107+
return regions.primary
109108
}
110109

111110
func otherRegionsFromEnv() []string {
112-
regions := os.Getenv("FLY_PREFLIGHT_TEST_FLY_REGIONS")
113-
if regions == "" {
114-
// Try to dynamically select best regions (get top 2, skip the first since it's primary)
111+
regions := selectRegionsFromEnv()
112+
return append([]string(nil), regions.other...)
113+
}
114+
115+
func selectRegionsFromEnv() selectedRegions {
116+
regionSelectionOnce.Do(func() {
117+
regions := strings.Fields(os.Getenv("FLY_PREFLIGHT_TEST_FLY_REGIONS"))
118+
if len(regions) > 0 {
119+
regionSelection.primary = regions[0]
120+
if len(regions) > 1 {
121+
regionSelection.other = append([]string(nil), regions[1:]...)
122+
}
123+
return
124+
}
125+
115126
best, err := getBestRegions(2)
116-
if err == nil && len(best) > 1 {
117-
return best[1:]
127+
if err == nil && len(best) > 0 {
128+
regionSelection.primary = best[0]
129+
if len(best) > 1 {
130+
regionSelection.other = append([]string(nil), best[1:]...)
131+
}
132+
terminal.Debugf("no region set with FLY_PREFLIGHT_TEST_FLY_REGIONS, auto-selected region with best capacity: %s", best[0])
133+
return
118134
}
119-
return nil
120-
}
121-
pieces := strings.Split(regions, " ")
122-
if len(pieces) > 1 {
123-
return pieces[1:]
124-
} else {
125-
return nil
135+
136+
regionSelection.primary = defaultRegion
137+
terminal.Warnf("no region set with FLY_PREFLIGHT_TEST_FLY_REGIONS, failed to auto-select (%v), using fallback: %s", err, defaultRegion)
138+
})
139+
140+
return selectedRegions{
141+
primary: regionSelection.primary,
142+
other: append([]string(nil), regionSelection.other...),
126143
}
127144
}
128145

0 commit comments

Comments
 (0)