@@ -27,14 +27,15 @@ import (
2727	"strings" 
2828
2929	"k8s.io/apimachinery/pkg/api/meta" 
30+ 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 
3031	"k8s.io/apimachinery/pkg/runtime" 
3132	"k8s.io/apimachinery/pkg/runtime/serializer/json" 
3233	"k8s.io/apimachinery/pkg/types" 
3334	"k8s.io/apimachinery/pkg/util/diff" 
3435	"k8s.io/client-go/rest" 
3536	"sigs.k8s.io/controller-runtime/pkg/client" 
37+ 	"sigs.k8s.io/controller-runtime/pkg/envtest" 
3638	"sigs.k8s.io/controller-runtime/pkg/manager" 
37- 	"sigs.k8s.io/controller-runtime/pkg/scheme" 
3839	"sigs.k8s.io/kubebuilder-declarative-pattern/mockkubeapiserver" 
3940	"sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/addon" 
4041	"sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/addon/pkg/loaders" 
@@ -61,37 +62,59 @@ type T interface {
6162	TempDir () string 
6263}
6364
64- func  NewValidator (t  T , b  * scheme.Builder ) * validator  {
65+ type  AddToSchemeFunc  func (s  * runtime.Scheme ) error 
66+ 
67+ func  NewValidator (t  T , env  * envtest.Environment , addToSchemeFuncs  ... AddToSchemeFunc ) * validator  {
68+ 	ctx  :=  context .TODO ()
69+ 	ctx , cancel  :=  context .WithCancel (ctx )
70+ 
6571	v  :=  & validator {T : t , scheme : runtime .NewScheme ()}
66- 	if  err  :=  b .AddToScheme (v .scheme ); err  !=  nil  {
67- 		t .Fatalf ("error from AddToScheme: %v" , err )
72+ 	for  _ , addToSchemeFunc  :=  range  addToSchemeFuncs  {
73+ 		if  err  :=  addToSchemeFunc (v .scheme ); err  !=  nil  {
74+ 			t .Fatalf ("error from AddToScheme: %v" , err )
75+ 		}
6876	}
6977
7078	v .T .Helper ()
7179	addon .Init ()
7280	v .findChannelsPath ()
7381
74- 	k8s , err  :=  mockkubeapiserver .NewMockKubeAPIServer (":0" )
75- 	if  err  !=  nil  {
76- 		t .Fatalf ("error building mock kube-apiserver: %v" , err )
77- 	}
82+ 	useEnvtest  :=  true 
83+ 	var  restConfig  * rest.Config 
84+ 	if  useEnvtest  {
85+ 		rc , err  :=  env .Start ()
86+ 		if  err  !=  nil  {
87+ 			t .Fatalf ("failed to start envtest kube-apiserver: %v" , err )
88+ 		}
89+ 		restConfig  =  rc 
90+ 		t .Cleanup (func () {
91+ 			if  err  :=  env .Stop (); err  !=  nil  {
92+ 				t .Errorf ("error stopping envtest: %v" , err )
93+ 			}
94+ 		})
7895
79- 	addr ,  err   :=   k8s . StartServing () 
80- 	if  err  !=   nil  { 
81- 		t . Errorf ( "error starting mock kube-apiserver: %v" ,  err ) 
82- 	} 
83- 	v . k8s   =   k8s 
96+ 	}  else  { 
97+ 		 k8s ,  err  :=   mockkubeapiserver . NewMockKubeAPIServer ( ":0" ) 
98+ 		if   err   !=   nil  { 
99+ 			 t . Fatalf ( "error building mock kube-apiserver: %v" ,  err ) 
100+ 		} 
84101
85- 	t . Cleanup ( func () { 
86- 		if  err  :=   k8s . Stop ();  err   !=  nil  {
87- 			t .Errorf ("error stopping  mock kube-apiserver: %v" , err )
102+ 		 addr ,  err   :=   k8s . StartServing () 
103+ 		if  err  !=  nil  {
104+ 			t .Errorf ("error starting  mock kube-apiserver: %v" , err )
88105		}
89- 	}) 
106+ 		 v . k8s   =   k8s 
90107
91- 	restConfig  :=  & rest.Config {
92- 		Host : addr .String (),
93- 	}
108+ 		t .Cleanup (func () {
109+ 			if  err  :=  k8s .Stop (); err  !=  nil  {
110+ 				t .Errorf ("error stopping mock kube-apiserver: %v" , err )
111+ 			}
112+ 		})
94113
114+ 		restConfig  =  & rest.Config {
115+ 			Host : addr .String (),
116+ 		}
117+ 	}
95118	mgr , err  :=  manager .New (restConfig , manager.Options {
96119		Scheme : v .scheme ,
97120	})
@@ -100,6 +123,25 @@ func NewValidator(t T, b *scheme.Builder) *validator {
100123	}
101124	v .client  =  mgr .GetClient ()
102125	v .mgr  =  mgr 
126+ 
127+ 	managerError  :=  make (chan  error )
128+ 	go  func () {
129+ 		err  :=  v .mgr .Start (ctx )
130+ 		managerError  <-  err 
131+ 	}()
132+ 
133+ 	// Wait for the manager to start 
134+ 	if  ! v .mgr .GetCache ().WaitForCacheSync (ctx ) {
135+ 		t .Fatalf ("error waiting for cache sync" )
136+ 	}
137+ 
138+ 	t .Cleanup (func () {
139+ 		// Cancel the context so the manager exits 
140+ 		cancel ()
141+ 		// Wait for manager to exit 
142+ 		<- managerError 
143+ 	})
144+ 
103145	return  v 
104146}
105147
@@ -186,7 +228,13 @@ func (v *validator) Client() client.Client {
186228	return  v .client 
187229}
188230
189- func  (v  * validator ) Validate (r  declarative.Reconciler ) {
231+ type  ValidateOptions  struct  {
232+ 	RewriteObjects  func (o  * unstructured.Unstructured )
233+ }
234+ 
235+ func  (v  * validator ) Validate (r  * declarative.Reconciler , options  ValidateOptions ) {
236+ 	ctx  :=  context .TODO ()
237+ 
190238	t  :=  v .T 
191239	t .Helper ()
192240
@@ -205,8 +253,6 @@ func (v *validator) Validate(r declarative.Reconciler) {
205253		t .Fatalf ("error reading dir %s: %v" , basedir , err )
206254	}
207255
208- 	ctx  :=  context .TODO ()
209- 
210256	for  _ , f  :=  range  files  {
211257		p  :=  filepath .Join (basedir , f .Name ())
212258		t .Logf ("Filepath: %s" , p )
@@ -217,7 +263,7 @@ func (v *validator) Validate(r declarative.Reconciler) {
217263		}
218264
219265		if  strings .HasSuffix (p , "~" ) {
220- 			// Ignore editor temp files (for sanity ) 
266+ 			// Ignore editor temp files (this makes development easier ) 
221267			t .Logf ("ignoring editor temp file %s" , p )
222268			continue 
223269		}
@@ -258,6 +304,7 @@ func (v *validator) Validate(r declarative.Reconciler) {
258304				if  err  :=  v .client .Create (ctx , obj ); err  !=  nil  {
259305					t .Errorf ("error creating resource in %s: %v" , p , err )
260306				}
307+ 				t .Logf ("created object %v %v/%v" , obj .GetObjectKind ().GroupVersionKind ().Kind , obj .GetNamespace (), obj .GetName ())
261308				objectsToCleanup  =  append (objectsToCleanup , obj )
262309			}
263310		}
@@ -291,6 +338,15 @@ func (v *validator) Validate(r declarative.Reconciler) {
291338			continue 
292339		}
293340
341+ 		{
342+ 			obj  :=  cr .(client.Object )
343+ 			if  err  :=  v .client .Create (ctx , obj ); err  !=  nil  {
344+ 				t .Errorf ("error creating resource in %s: %v" , p , err )
345+ 			}
346+ 			t .Logf ("created object %v %v/%v" , obj .GetObjectKind ().GroupVersionKind ().Kind , obj .GetNamespace (), obj .GetName ())
347+ 			objectsToCleanup  =  append (objectsToCleanup , obj )
348+ 		}
349+ 
294350		namespace , err  :=  metadataAccessor .Namespace (cr )
295351		if  err  !=  nil  {
296352			t .Errorf ("error getting namespace in %s: %v" , p , err )
@@ -324,6 +380,9 @@ func (v *validator) Validate(r declarative.Reconciler) {
324380					b .WriteString ("---\n " )
325381				}
326382				u  :=  o .UnstructuredObject ()
383+ 				if  options .RewriteObjects  !=  nil  {
384+ 					options .RewriteObjects (u )
385+ 				}
327386				if  err  :=  yamlizer .Encode (u , & b ); err  !=  nil  {
328387					t .Fatalf ("error encoding to yaml: %v" , err )
329388				}
@@ -336,15 +395,19 @@ func (v *validator) Validate(r declarative.Reconciler) {
336395		{
337396			b , err  :=  os .ReadFile (expectedPath )
338397			if  err  !=  nil  {
339- 				t .Errorf ("error reading file %s: %v" , expectedPath , err )
340- 				continue 
398+ 				if  os .IsNotExist (err ) &&  ShouldWriteGoldenOutput (t ) {
399+ 					// We'll create the file below 
400+ 				} else  {
401+ 					t .Errorf ("error reading file %s: %v" , expectedPath , err )
402+ 					continue 
403+ 				}
341404			}
342405			expectedYAML  =  string (b )
343406		}
344407
345408		if  actualYAML  !=  expectedYAML  {
346- 			if  os . Getenv ( "HACK_AUTOFIX_EXPECTED_OUTPUT" )  !=   ""  {
347- 				t .Logf ("HACK_AUTOFIX_EXPECTED_OUTPUT  is set; replacing expected output in %s" , expectedPath )
409+ 			if  ShouldWriteGoldenOutput ( t )  {
410+ 				t .Logf ("WRITE_GOLDEN_OUTPUT  is set; replacing expected output in %s" , expectedPath )
348411				if  err  :=  os .WriteFile (expectedPath , []byte (actualYAML ), 0644 ); err  !=  nil  {
349412					t .Fatalf ("error writing expected output to %s: %v" , expectedPath , err )
350413				}
@@ -357,15 +420,27 @@ func (v *validator) Validate(r declarative.Reconciler) {
357420			}
358421
359422			t .Errorf ("unexpected diff between actual and expected YAML. See previous output for details." )
360- 			t .Logf (`To regenerate the output based on this result, rerun this test with HACK_AUTOFIX_EXPECTED_OUTPUT ="true"` )
423+ 			t .Logf (`To regenerate the output based on this result, rerun this test with WRITE_GOLDEN_OUTPUT ="true"` )
361424		}
362425
363426		for  _ , objectToCleanup  :=  range  objectsToCleanup  {
364427			if  err  :=  v .client .Delete (ctx , objectToCleanup ); err  !=  nil  {
365428				t .Errorf ("error deleting object: %v" , err )
366429			}
367430		}
431+ 
432+ 	}
433+ }
434+ 
435+ func  ShouldWriteGoldenOutput (t  T ) bool  {
436+ 	if  os .Getenv ("HACK_AUTOFIX_EXPECTED_OUTPUT" ) !=  ""  {
437+ 		t .Logf ("HACK_AUTOFIX_EXPECTED_OUTPUT is set, please switch to use WRITE_GOLDEN_OUTPUT.  This may be an test failure in future versions." )
438+ 		return  true 
439+ 	}
440+ 	if  os .Getenv ("WRITE_GOLDEN_OUTPUT" ) !=  ""  {
441+ 		return  true 
368442	}
443+ 	return  false 
369444}
370445
371446func  diffFiles (t  T , expectedPath , actual  string ) error  {
0 commit comments