Skip to content

Commit 10a798d

Browse files
mjudeikisclaude
andcommitted
test(e2e): modernize the quickstart suite onto the current bootstrap
The suite predated the provider bootstrap refactor: it applied only a CatalogEntry, into root:kedge:providers, and expected the hub to materialize the APIExport/schemas/bind-grant. The current flow applies Provider + CatalogEntry into root:kedge:system:providers, the hub's Provider controller materializes the sub-workspace + SA + provider-token Secret, and `quickstart-provider init` (run with the minted SA kubeconfig) authors the APIExport, schemas, and bind grant. Move the bootstrap into TestMain, mirroring the infraprovider suite: apply provider.yaml + manifest.yaml (URL-overridden, with the API-servable retry), wait for the provider-token Secret, mint the workspace-scoped runtime kubeconfig around it (exactly what `make init-provider-quickstart` scripts), run init, then serve. TestACatalogProvisioning stops applying manifests and reads the CatalogEntry from root:kedge:system:providers; the sub-workspace artifact assertions are unchanged — the artifacts just come from the Provider controller + init now. Re-enable the suite in the e2e-providers CI job (descoped in #417 when its first-ever CI run exposed the bitrot). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent b01a9a1 commit 10a798d

3 files changed

Lines changed: 233 additions & 92 deletions

File tree

.github/workflows/e2e.yaml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,13 @@ permissions:
1313
jobs:
1414
# Subprocess provider suites: hub with embedded kcp + provider binaries as
1515
# host processes, driven over HTTP + kcp dynamic clients. No kind, no Helm,
16-
# no Docker — the cheapest full-kcp coverage we have.
17-
#
18-
# NOTE: the older quickstart suite (make e2e-provider) predates the
19-
# provider bootstrap refactor (Provider CR + provider-run init) and fails
20-
# against the current hub — it is deliberately NOT wired here until it is
21-
# modernized onto the same bootstrap the infraprovider suite uses.
16+
# no Docker — the cheapest full-kcp coverage we have. The suites share the
17+
# embedded kcp's fixed etcd port (2380), so they run sequentially in one
18+
# job rather than as a matrix.
2219
e2e-providers:
2320
name: Provider suites (embedded kcp subprocesses)
2421
runs-on: ubuntu-latest
25-
timeout-minutes: 30
22+
timeout-minutes: 45
2623
steps:
2724
- uses: actions/checkout@v4
2825

@@ -33,6 +30,9 @@ jobs:
3330
- name: Run infrastructure provider e2e
3431
run: make e2e-infra-provider
3532

33+
- name: Run quickstart provider e2e
34+
run: make e2e-provider
35+
3636
e2e-standalone:
3737
name: Standalone (embedded kcp + static token)
3838
runs-on: ubuntu-latest

test/e2e/suites/provider/main_test.go

Lines changed: 137 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,22 @@ limitations under the License.
1616

1717
// Package provider implements an end-to-end suite for the kedge provider
1818
// extension surface. It starts the kedge-hub with embedded kcp and the
19-
// reference quickstart provider as host subprocesses, then exercises the
20-
// full lifecycle: catalog provisioning (sub-workspace + APIResourceSchema
21-
// + APIExport + RBAC), the /api/providers and /ui|services/providers
22-
// proxies, tenant Enable via direct APIBinding, and heartbeat freshness.
19+
// reference quickstart provider as host subprocesses, following the current
20+
// bootstrap flow: Provider + CatalogEntry applied into
21+
// root:kedge:system:providers (the hub's Provider controller materializes
22+
// the sub-workspace + SA + provider-token), then `quickstart-provider init`
23+
// with the minted SA kubeconfig (APIExport + schemas + bind grant), then
24+
// serve. The tests exercise the full lifecycle: catalog provisioning, the
25+
// /api/providers and /ui|services/providers proxies, tenant Enable via
26+
// direct APIBinding, and heartbeat freshness.
2327
//
2428
// Runs without kind/Helm/Dex. Intentionally lighter-weight than the
2529
// standalone suite so iteration on the provider plumbing is fast.
2630
package provider
2731

2832
import (
2933
"context"
34+
"encoding/base64"
3035
"fmt"
3136
"io"
3237
"net"
@@ -39,6 +44,9 @@ import (
3944
"syscall"
4045
"testing"
4146
"time"
47+
48+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
49+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
4250
)
4351

4452
// Suite-shared state populated by TestMain.
@@ -105,25 +113,7 @@ func TestMain(m *testing.M) {
105113
}
106114
fmt.Fprintf(os.Stderr, "hub started (pid=%d, log=%s)\n", hubCmd.Process.Pid, hubLog.Name())
107115

108-
provLog, _ := os.Create(filepath.Join(dataDir, "provider.log"))
109-
provCmd := exec.Command(filepath.Join(repoRoot, "bin", "quickstart-provider"))
110-
provCmd.Env = append(os.Environ(),
111-
"PORT="+providerPort,
112-
"KEDGE_HUB_URL="+hubURL,
113-
"KEDGE_HUB_TOKEN="+staticToken,
114-
"KEDGE_PROVIDER_NAME=quickstart",
115-
)
116-
provCmd.Stdout = provLog
117-
provCmd.Stderr = provLog
118-
provCmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
119-
if err := provCmd.Start(); err != nil {
120-
killGroup(hubCmd)
121-
fmt.Fprintln(os.Stderr, "start provider:", err)
122-
os.Exit(1)
123-
}
124-
fmt.Fprintf(os.Stderr, "quickstart-provider started (pid=%d, port=:%s)\n", provCmd.Process.Pid, providerPort)
125-
126-
// Cleanup on any exit path.
116+
var provCmd *exec.Cmd
127117
cleanup := func() {
128118
killGroup(hubCmd)
129119
killGroup(provCmd)
@@ -140,11 +130,6 @@ func TestMain(m *testing.M) {
140130
fmt.Fprintln(os.Stderr, "hub never ready:", err)
141131
os.Exit(1)
142132
}
143-
if err := waitReady("http://127.0.0.1:"+providerPort+"/healthz", 10*time.Second); err != nil {
144-
cleanup()
145-
fmt.Fprintln(os.Stderr, "quickstart never ready:", err)
146-
os.Exit(1)
147-
}
148133

149134
// Snapshot the admin token from the kubeconfig the hub just wrote.
150135
tok, err := extractToken(filepath.Join(dataDir, "kcp", "admin.kubeconfig"))
@@ -155,11 +140,135 @@ func TestMain(m *testing.M) {
155140
}
156141
adminToken = tok
157142

143+
// Provisioning: Provider + CatalogEntry into root:kedge:system:providers
144+
// (mirrors `make install-provider-quickstart`); the hub's Provider
145+
// controller materializes root:kedge:providers:quickstart, the provider
146+
// SA, and the provider-token Secret.
147+
if err := applyQuickstartManifests(); err != nil {
148+
cleanup()
149+
fmt.Fprintln(os.Stderr, "apply quickstart manifests:", err)
150+
os.Exit(1)
151+
}
152+
153+
// Mint the SA runtime kubeconfig from the provider-token Secret (mirrors
154+
// `make init-provider-quickstart`) and run `quickstart-provider init` —
155+
// the APIExport/schemas/bind-grant come from init, not the hub.
156+
runtimeKubeconfig := filepath.Join(dataDir, "quickstart-runtime.kubeconfig")
157+
if err := mintRuntimeKubeconfig(runtimeKubeconfig, 2*time.Minute); err != nil {
158+
cleanup()
159+
fmt.Fprintln(os.Stderr, "mint runtime kubeconfig:", err)
160+
os.Exit(1)
161+
}
162+
initLog, err := os.Create(filepath.Join(dataDir, "init.log"))
163+
if err != nil {
164+
cleanup()
165+
fmt.Fprintln(os.Stderr, "create init.log:", err)
166+
os.Exit(1)
167+
}
168+
initCmd := exec.Command(filepath.Join(repoRoot, "bin", "quickstart-provider"), "init")
169+
initCmd.Env = append(os.Environ(),
170+
"KEDGE_PROVIDER_KUBECONFIG="+runtimeKubeconfig,
171+
"QUICKSTART_WORKSPACE_PATH="+workspacePath,
172+
// Same as the Makefile dev flow: no chart schemas dir on a host run.
173+
"KEDGE_SCHEMAS_DIR=/nonexistent",
174+
)
175+
initCmd.Stdout = initLog
176+
initCmd.Stderr = initLog
177+
if err := initCmd.Run(); err != nil {
178+
cleanup()
179+
fmt.Fprintf(os.Stderr, "quickstart init failed: %v (log: %s)\n", err, initLog.Name())
180+
os.Exit(1)
181+
}
182+
183+
provLog, err := os.Create(filepath.Join(dataDir, "provider.log"))
184+
if err != nil {
185+
cleanup()
186+
fmt.Fprintln(os.Stderr, "create provider.log:", err)
187+
os.Exit(1)
188+
}
189+
provCmd = exec.Command(filepath.Join(repoRoot, "bin", "quickstart-provider"))
190+
provCmd.Env = append(os.Environ(),
191+
"PORT="+providerPort,
192+
"KEDGE_HUB_URL="+hubURL,
193+
"KEDGE_HUB_TOKEN="+staticToken,
194+
"KEDGE_PROVIDER_NAME=quickstart",
195+
)
196+
provCmd.Stdout = provLog
197+
provCmd.Stderr = provLog
198+
provCmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
199+
if err := provCmd.Start(); err != nil {
200+
cleanup()
201+
fmt.Fprintln(os.Stderr, "start provider:", err)
202+
os.Exit(1)
203+
}
204+
fmt.Fprintf(os.Stderr, "quickstart-provider started (pid=%d, port=:%s)\n", provCmd.Process.Pid, providerPort)
205+
206+
if err := waitReady("http://127.0.0.1:"+providerPort+"/healthz", 30*time.Second); err != nil {
207+
cleanup()
208+
fmt.Fprintln(os.Stderr, "quickstart never ready:", err)
209+
os.Exit(1)
210+
}
211+
158212
code := m.Run()
159213
cleanup()
160214
os.Exit(code)
161215
}
162216

217+
// mintRuntimeKubeconfig waits for the Provider controller to populate the
218+
// provider-token Secret in the sub-workspace and writes a workspace-scoped
219+
// kubeconfig around it — the same credential the provider pod mounts.
220+
func mintRuntimeKubeconfig(path string, timeout time.Duration) error {
221+
cl, err := kcpDynamicRaw(workspacePath, adminToken)
222+
if err != nil {
223+
return err
224+
}
225+
deadline := time.Now().Add(timeout)
226+
var token string
227+
var lastErr string
228+
for time.Now().Before(deadline) {
229+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
230+
sec, err := cl.Resource(secretGVR).Namespace("default").Get(ctx, "provider-token", metav1.GetOptions{})
231+
cancel()
232+
if err != nil {
233+
lastErr = err.Error()
234+
} else {
235+
enc, _, _ := unstructured.NestedString(sec.Object, "data", "token")
236+
if enc != "" {
237+
raw, err := base64.StdEncoding.DecodeString(enc)
238+
if err != nil {
239+
return fmt.Errorf("decode provider-token: %w", err)
240+
}
241+
token = string(raw)
242+
break
243+
}
244+
lastErr = "provider-token Secret exists but token not yet populated"
245+
}
246+
time.Sleep(2 * time.Second)
247+
}
248+
if token == "" {
249+
return fmt.Errorf("provider-token never populated: %s", lastErr)
250+
}
251+
kc := fmt.Sprintf(`apiVersion: v1
252+
kind: Config
253+
clusters:
254+
- name: kedge
255+
cluster:
256+
server: %s/clusters/%s
257+
insecure-skip-tls-verify: true
258+
contexts:
259+
- name: kedge
260+
context:
261+
cluster: kedge
262+
user: kedge
263+
current-context: kedge
264+
users:
265+
- name: kedge
266+
user:
267+
token: %s
268+
`, kcpServer, workspacePath, token)
269+
return os.WriteFile(path, []byte(kc), 0o600)
270+
}
271+
163272
// build runs `make build-hub build-quickstart-provider` so the test runs
164273
// against current source even when the user hasn't built manually.
165274
func build(root string) error {

0 commit comments

Comments
 (0)