diff --git a/devspace.yaml b/devspace.yaml index 0615b26..3a3a7a0 100644 --- a/devspace.yaml +++ b/devspace.yaml @@ -501,6 +501,12 @@ hooks: silent: true events: ["before:deploy:metallb"] + - name: wait-for-metallb-hook + command: | + echo >&2 "I: Waiting for MetalLB controller rollout..." + kubectl rollout status deployment/metallb-controller -n metallb-system --timeout=300s + events: ["after:deploy:metallb"] + - name: update-cluster-dns-hook-darwin os: darwin command: devspace run update-cluster-dns @@ -518,6 +524,7 @@ hooks: echo >&2 "I: Waiting for root CA to be available..." sleep 5 done + echo >&2 "I: Root CA imported successfully." events: ["after:deploy:cert-chain"] - name: cert-chain-hook-linux @@ -551,6 +558,7 @@ pipelines: PHASES=( "etcd gateway-api-crds" "cert-manager metallb reflector prometheus" + "metallb-resources" "cert-chain trust-manager" "istio-base" "istiod" @@ -572,6 +580,7 @@ pipelines: create_deployments "${final[@]}" fi done + create_deployments --all --except ${PHASES[@]} if is_in "grafana" "$enabled"; then # These dashboard ConfigMaps are too large for client-side apply's last-applied annotation. diff --git a/tests/e2e/cmd/smoke/main.go b/tests/e2e/cmd/smoke/main.go index 2c5db69..e834cf0 100644 --- a/tests/e2e/cmd/smoke/main.go +++ b/tests/e2e/cmd/smoke/main.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "io" "os" "os/exec" "path/filepath" @@ -109,10 +110,15 @@ func run() error { } testEnv := append(env, "CGO_ENABLED=1") - goArgs := append([]string{"test", "-count=1", "-v", "-timeout", cfg.testTimeout.String()}, cfg.testArgs...) + goArgs := append([]string{"test", "-count=1", "-timeout", cfg.testTimeout.String()}, cfg.testArgs...) goArgs = append(goArgs, "./tests/install") - if err := runStep(ctx, testEnv, "go", goArgs...); err != nil { + if output, err := runStepCapture(ctx, testEnv, "go", goArgs...); err != nil { collectDiagnostics(env, cfg.diagnosticTimeout) + summary := formatInstallTestOutputSummary(output) + if summary != "" { + writeStepSummary(summary) + fmt.Fprintln(os.Stderr, summary) + } return err } @@ -282,6 +288,25 @@ func runStep(ctx context.Context, env []string, name string, args ...string) err return nil } +func runStepCapture(ctx context.Context, env []string, name string, args ...string) (string, error) { + fmt.Printf("smoke: running %s %s\n", name, strings.Join(args, " ")) + cmd := exec.CommandContext(ctx, name, args...) + cmd.Env = env + + var output bytes.Buffer + cmd.Stdout = io.MultiWriter(os.Stdout, &output) + cmd.Stderr = io.MultiWriter(os.Stderr, &output) + + if err := cmd.Run(); err != nil { + if ctx.Err() != nil { + return output.String(), fmt.Errorf("%s %s timed out: %w", name, strings.Join(args, " "), ctx.Err()) + } + return output.String(), fmt.Errorf("%s %s failed: %w", name, strings.Join(args, " "), err) + } + + return output.String(), nil +} + func commandOutput(ctx context.Context, env []string, name string, args ...string) (string, error) { cmd := exec.CommandContext(ctx, name, args...) cmd.Env = env @@ -294,6 +319,37 @@ func commandOutput(ctx context.Context, env []string, name string, args ...strin return stdout.String(), nil } +func formatInstallTestOutputSummary(output string) string { + output = strings.TrimSpace(output) + if output == "" { + return "" + } + + var b strings.Builder + b.WriteString("## Install test output\n\n```text\n") + b.WriteString(output) + b.WriteString("\n```") + return b.String() +} + +func writeStepSummary(summary string) { + path := os.Getenv("GITHUB_STEP_SUMMARY") + if path == "" || summary == "" { + return + } + + file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) + if err != nil { + fmt.Fprintf(os.Stderr, "smoke: failed to write GitHub step summary: %v\n", err) + return + } + defer file.Close() + + if _, err := file.WriteString(summary + "\n"); err != nil { + fmt.Fprintf(os.Stderr, "smoke: failed to append GitHub step summary: %v\n", err) + } +} + func splitArgs(value string) []string { return strings.Fields(value) } diff --git a/tests/install/host_routes.go b/tests/install/host_routes.go index e8d91bd..167f9a9 100644 --- a/tests/install/host_routes.go +++ b/tests/install/host_routes.go @@ -3,7 +3,9 @@ package install_test import ( "crypto/tls" "crypto/x509" + "errors" "io" + "net" "net/http" "strings" "testing" @@ -19,20 +21,44 @@ func assertHTTPSGet(t *testing.T, description, url string) { } client := &http.Client{ - Timeout: 15 * time.Second, + Timeout: 5 * time.Second, Transport: &http.Transport{ TLSClientConfig: &tls.Config{RootCAs: pool, MinVersion: tls.VersionTLS12}, }, } - resp, err := client.Get(url) - if err != nil { - t.Fatalf("%s through gateway failed: %v", description, err) + + deadline := time.Now().Add(45 * time.Second) + for { + resp, err := client.Get(url) + if err == nil { + defer resp.Body.Close() + body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096)) + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + t.Fatalf("%s returned %s: %s", description, resp.Status, strings.TrimSpace(string(body))) + } + return + } + + if time.Now().After(deadline) || !isRetryableHTTPGetError(err) { + t.Fatalf("%s through gateway failed: %v", description, err) + } + + time.Sleep(2 * time.Second) } - defer resp.Body.Close() - body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096)) - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - t.Fatalf("%s returned %s: %s", description, resp.Status, strings.TrimSpace(string(body))) +} + +func isRetryableHTTPGetError(err error) bool { + var dnsErr *net.DNSError + if errors.As(err, &dnsErr) { + return true + } + + var netErr net.Error + if errors.As(err, &netErr) { + return true } + + return false } func httpbinRouteInstalled(t *testing.T) bool {