Skip to content

Commit 81f3fb2

Browse files
authored
Merge pull request #299 from techulus/automation/release/v0.59.0
Release v0.59.0
2 parents 02db10c + 6d5ade5 commit 81f3fb2

77 files changed

Lines changed: 1755 additions & 303 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.agents/resume‎

Lines changed: 0 additions & 4 deletions
This file was deleted.

‎.agents/setup‎

Lines changed: 0 additions & 53 deletions
This file was deleted.

‎.github/workflows/agent-ci.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jobs:
4343
run: go vet ./...
4444

4545
- name: Install staticcheck
46-
run: GOBIN=$PWD/.bin go install honnef.co/go/tools/cmd/staticcheck@latest
46+
run: GOBIN=$PWD/.bin go install honnef.co/go/tools/cmd/staticcheck@v0.7.0
4747

4848
- name: Staticcheck
4949
run: ./.bin/staticcheck ./...

‎.github/workflows/cli-ci.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jobs:
4343
run: go vet ./...
4444

4545
- name: Install staticcheck
46-
run: GOBIN=$PWD/.bin go install honnef.co/go/tools/cmd/staticcheck@latest
46+
run: GOBIN=$PWD/.bin go install honnef.co/go/tools/cmd/staticcheck@v0.7.0
4747

4848
- name: Staticcheck
4949
run: ./.bin/staticcheck ./...

‎.github/workflows/updater-ci.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ jobs:
4545
run: go vet ./...
4646

4747
- name: Install staticcheck
48-
run: GOBIN=$PWD/.bin go install honnef.co/go/tools/cmd/staticcheck@latest
48+
run: GOBIN=$PWD/.bin go install honnef.co/go/tools/cmd/staticcheck@v0.7.0
4949

5050
- name: Staticcheck
5151
run: ./.bin/staticcheck ./...

‎agent/internal/traefik/reload.go‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
const (
1616
lastReloadSuccessMetric = "traefik_config_last_reload_success"
1717
pendingReloadMarkerName = ".routing-reload-pending"
18+
metricsReadyTimeout = 15 * time.Second
1819
)
1920

2021
var (
@@ -30,6 +31,22 @@ func LastSuccessfulReload() (time.Time, error) {
3031
return readLastSuccessfulReload()
3132
}
3233

34+
func waitForMetricsReady(timeout time.Duration) error {
35+
deadline := time.Now().Add(timeout)
36+
var lastErr error
37+
for {
38+
if _, err := LastSuccessfulReload(); err == nil {
39+
return nil
40+
} else {
41+
lastErr = err
42+
}
43+
if time.Now().After(deadline) {
44+
return fmt.Errorf("traefik metrics did not become ready within %s: %w", timeout, lastErr)
45+
}
46+
time.Sleep(reloadPollInterval)
47+
}
48+
}
49+
3350
func fetchLastSuccessfulReload() (time.Time, error) {
3451
response, err := metricsHTTPClient.Get(traefikMetricsURL)
3552
if err != nil {

‎agent/internal/traefik/reload_test.go‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,54 @@ traefik_config_last_reload_success 1.725e+09
2020
}
2121
}
2222

23+
func TestWaitForMetricsReadyRetriesTemporaryErrors(t *testing.T) {
24+
originalReader := readLastSuccessfulReload
25+
originalPollInterval := reloadPollInterval
26+
t.Cleanup(func() {
27+
readLastSuccessfulReload = originalReader
28+
reloadPollInterval = originalPollInterval
29+
})
30+
31+
attempts := 0
32+
readLastSuccessfulReload = func() (time.Time, error) {
33+
attempts++
34+
if attempts < 3 {
35+
return time.Time{}, os.ErrNotExist
36+
}
37+
return time.Now(), nil
38+
}
39+
reloadPollInterval = time.Millisecond
40+
41+
if err := waitForMetricsReady(50 * time.Millisecond); err != nil {
42+
t.Fatal(err)
43+
}
44+
if attempts != 3 {
45+
t.Fatalf("metrics read attempted %d times, want 3", attempts)
46+
}
47+
}
48+
49+
func TestWaitForMetricsReadyTimesOut(t *testing.T) {
50+
originalReader := readLastSuccessfulReload
51+
originalPollInterval := reloadPollInterval
52+
t.Cleanup(func() {
53+
readLastSuccessfulReload = originalReader
54+
reloadPollInterval = originalPollInterval
55+
})
56+
57+
readLastSuccessfulReload = func() (time.Time, error) {
58+
return time.Time{}, os.ErrDeadlineExceeded
59+
}
60+
reloadPollInterval = time.Millisecond
61+
62+
err := waitForMetricsReady(5 * time.Millisecond)
63+
if err == nil {
64+
t.Fatal("metrics readiness wait unexpectedly succeeded")
65+
}
66+
if !strings.Contains(err.Error(), "traefik metrics did not become ready within 5ms") {
67+
t.Fatalf("unexpected timeout error: %v", err)
68+
}
69+
}
70+
2371
func TestDynamicConfigReloadedRequiresReloadAtOrAfterNewestFile(t *testing.T) {
2472
originalDir := dynamicConfigDir
2573
originalReader := readLastSuccessfulReload

‎agent/internal/traefik/static.go‎

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"os"
77
"os/exec"
88
"reflect"
9-
"time"
109

1110
"gopkg.in/yaml.v3"
1211
)
@@ -16,6 +15,7 @@ const (
1615
metricsEntryPointAddr = "127.0.0.1:9100"
1716
)
1817

18+
// Whole-number buckets must be ints to match yaml.v3's decoded types.
1919
var prometheusLatencyBuckets = []interface{}{
2020
0.005,
2121
0.01,
@@ -26,12 +26,12 @@ var prometheusLatencyBuckets = []interface{}{
2626
0.25,
2727
0.5,
2828
0.75,
29-
1.0,
29+
1,
3030
2.5,
31-
5.0,
32-
10.0,
33-
30.0,
34-
60.0,
31+
5,
32+
10,
33+
30,
34+
60,
3535
}
3636

3737
func validateStaticConfig(data []byte) error {
@@ -218,6 +218,5 @@ func ReloadTraefik() error {
218218
return fmt.Errorf("failed to restart traefik: %w", err)
219219
}
220220
log.Printf("[traefik] restarted traefik to apply static config changes")
221-
time.Sleep(2 * time.Second)
222-
return nil
221+
return waitForMetricsReady(metricsReadyTimeout)
223222
}

‎agent/internal/traefik/static_test.go‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package traefik
22

3-
import "testing"
3+
import (
4+
"testing"
5+
6+
"gopkg.in/yaml.v3"
7+
)
48

59
func TestEnsurePrometheusMetricsConfigAddsPrivateMetricsEndpoint(t *testing.T) {
610
config := map[string]interface{}{
@@ -44,7 +48,17 @@ func TestEnsurePrometheusMetricsConfigIsStable(t *testing.T) {
4448
if !ensurePrometheusMetricsConfig(config) {
4549
t.Fatal("expected first call to modify config")
4650
}
47-
if ensurePrometheusMetricsConfig(config) {
51+
52+
data, err := yaml.Marshal(config)
53+
if err != nil {
54+
t.Fatalf("failed to marshal config: %v", err)
55+
}
56+
var roundTripped map[string]interface{}
57+
if err := yaml.Unmarshal(data, &roundTripped); err != nil {
58+
t.Fatalf("failed to unmarshal config: %v", err)
59+
}
60+
61+
if ensurePrometheusMetricsConfig(roundTripped) {
4862
t.Fatal("expected second call to be stable")
4963
}
5064
}

‎web/actions/compose.ts‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { db } from "@/db";
55
import { services } from "@/db/schema";
66
import { requireDeveloperRole } from "@/lib/auth";
77
import { parseComposeYaml } from "@/lib/compose-parser";
8+
import { reportServerError } from "@/lib/server-errors";
89
import {
910
addServiceVolume,
1011
createService,
@@ -144,6 +145,9 @@ export async function importCompose(
144145
volume.containerPath,
145146
);
146147
} catch (e) {
148+
reportServerError(e, "compose-import.volume.add", {
149+
tags: { serviceId: result.id },
150+
});
147151
warnings.push({
148152
service: finalName,
149153
field: "volumes",
@@ -182,9 +186,16 @@ export async function importCompose(
182186
for (const serviceId of createdServiceIds) {
183187
try {
184188
await db.delete(services).where(eq(services.id, serviceId));
185-
} catch {}
189+
} catch (cleanupError) {
190+
reportServerError(cleanupError, "compose-import.cleanup", {
191+
tags: { serviceId },
192+
});
193+
}
186194
}
187195

196+
reportServerError(error, "compose-import.create", {
197+
tags: { projectId, environmentId },
198+
});
188199
return {
189200
success: false,
190201
created: [],

0 commit comments

Comments
 (0)