@@ -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.
2630package provider
2731
2832import (
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.
165274func build (root string ) error {
0 commit comments