Repository: knative/pkg
Pull request #2611 (2022-10-17 06:16:27)

Filter reactor FieldErrors on validation to only error-level alerts.
See https://github.com/knative/serving/pull/13399#issuecomment-1280033374

When we added warning-level `api.FieldErrors`, we missed filtering the warnings from `ValidateCreates` and `ValidateUpdates`.
For https://github.com/knative/serving/pull/13399, we want to be able to issue warnings on insecure default values for containers, but we don't want to force all our existing tests to use secure default values (and we want to validate that the insecure values will still work for controllers, etc).

knative-prow[bot]: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/knative/pkg/pull/2611#" title="Author self-approved">evankanderson</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=knative%2Fpkg).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[reconciler/OWNERS](https://github.com/knative/pkg/blob/main/reconciler/OWNERS)~~ [evankanderson]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
codecov[bot]: # [Codecov](https://codecov.io/gh/knative/pkg/pull/2611?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative) Report
Base: **81.33**% // Head: **81.28**% // Decreases project coverage by **`-0.04%`** :warning:
> Coverage data is based on head [(`1b1ea86`)](https://codecov.io/gh/knative/pkg/pull/2611?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative) compared to base [(`714b763`)](https://codecov.io/gh/knative/pkg/commit/714b7630a836ef3952826849f31fe9bc16493218?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative).
> Patch coverage: 61.90% of modified lines in pull request are covered.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #2611      +/-   ##
==========================================
- Coverage   81.33%   81.28%   -0.05%     
==========================================
  Files         162      162              
  Lines        9824     9845      +21     
==========================================
+ Hits         7990     8003      +13     
- Misses       1595     1603       +8     
  Partials      239      239              
```


| [Impacted Files](https://codecov.io/gh/knative/pkg/pull/2611?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative) | Coverage Δ | |
|---|---|---|
| [metrics/config\_observability.go](https://codecov.io/gh/knative/pkg/pull/2611/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative#diff-bWV0cmljcy9jb25maWdfb2JzZXJ2YWJpbGl0eS5nbw==) | `80.35% <61.90%> (-11.08%)` | :arrow_down: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/knative/pkg/pull/2611?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative).


Patch:
diff --git a/OWNERS_ALIASES b/OWNERS_ALIASES
index a8d05da71..1e7399ba5 100644
--- a/OWNERS_ALIASES
+++ b/OWNERS_ALIASES
@@ -157,7 +157,6 @@ aliases:
   productivity-reviewers:
   - albertomilan
   - evankanderson
-  - gerardo-lc
   - mgencur
   - shinigambit
   productivity-wg-leads:
diff --git a/apis/field_error.go b/apis/field_error.go
index ddd4838af..cacea2f3c 100644
--- a/apis/field_error.go
+++ b/apis/field_error.go
@@ -83,11 +83,12 @@ var _ error = (*FieldError)(nil)
 
 // ViaField is used to propagate a validation error along a field access.
 // For example, if a type recursively validates its "spec" via:
-//   if err := foo.Spec.Validate(); err != nil {
-//     // Augment any field paths with the context that they were accessed
-//     // via "spec".
-//     return err.ViaField("spec")
-//   }
+//
+//	if err := foo.Spec.Validate(); err != nil {
+//	  // Augment any field paths with the context that they were accessed
+//	  // via "spec".
+//	  return err.ViaField("spec")
+//	}
 func (fe *FieldError) ViaField(prefix ...string) *FieldError {
 	if fe == nil {
 		return nil
@@ -114,11 +115,12 @@ func (fe *FieldError) ViaField(prefix ...string) *FieldError {
 
 // ViaIndex is used to attach an index to the next ViaField provided.
 // For example, if a type recursively validates a parameter that has a collection:
-//  for i, c := range spec.Collection {
-//    if err := doValidation(c); err != nil {
-//      return err.ViaIndex(i).ViaField("collection")
-//    }
-//  }
+//
+//	for i, c := range spec.Collection {
+//	  if err := doValidation(c); err != nil {
+//	    return err.ViaIndex(i).ViaField("collection")
+//	  }
+//	}
 func (fe *FieldError) ViaIndex(index int) *FieldError {
 	return fe.ViaField(asIndex(index))
 }
@@ -130,11 +132,12 @@ func (fe *FieldError) ViaFieldIndex(field string, index int) *FieldError {
 
 // ViaKey is used to attach a key to the next ViaField provided.
 // For example, if a type recursively validates a parameter that has a collection:
-//  for k, v := range spec.Bag {
-//    if err := doValidation(v); err != nil {
-//      return err.ViaKey(k).ViaField("bag")
-//    }
-//  }
+//
+//	for k, v := range spec.Bag {
+//	  if err := doValidation(v); err != nil {
+//	    return err.ViaKey(k).ViaField("bag")
+//	  }
+//	}
 func (fe *FieldError) ViaKey(key string) *FieldError {
 	return fe.ViaField(asKey(key))
 }
@@ -145,7 +148,8 @@ func (fe *FieldError) ViaFieldKey(field, key string) *FieldError {
 }
 
 // At is a way to alter the level of the diagnostics held in this FieldError.
-//    ErrMissingField("foo").At(WarningLevel)
+//
+//	ErrMissingField("foo").At(WarningLevel)
 func (fe *FieldError) At(l DiagnosticLevel) *FieldError {
 	if fe == nil {
 		return nil
@@ -166,9 +170,10 @@ func (fe *FieldError) At(l DiagnosticLevel) *FieldError {
 }
 
 // Filter is a way to access the set of diagnostics having a particular level.
-//    if err := x.Validate(ctx).Filter(ErrorLevel); err != nil {
-//       return err
-//    }
+//
+//	if err := x.Validate(ctx).Filter(ErrorLevel); err != nil {
+//	   return err
+//	}
 func (fe *FieldError) Filter(l DiagnosticLevel) *FieldError {
 	if fe == nil {
 		return nil
@@ -251,10 +256,15 @@ func (fe *FieldError) normalized() []*FieldError {
 	return errors
 }
 
+// WrappedErrors returns the value of the errors after normalizing and deduping using merge().
+func (fe *FieldError) WrappedErrors() []*FieldError {
+	return merge(fe.normalized())
+}
+
 // Error implements error
 func (fe *FieldError) Error() string {
 	// Get the list of errors as a flat merged list.
-	normedErrors := merge(fe.normalized())
+	normedErrors := fe.WrappedErrors()
 	errs := make([]string, 0, len(normedErrors))
 	for _, e := range normedErrors {
 		if e.Details == "" {
@@ -282,10 +292,11 @@ func asKey(key string) string {
 
 // flatten takes in a array of path components and looks for chances to flatten
 // objects that have index prefixes, examples:
-//   err([0]).ViaField(bar).ViaField(foo) -> foo.bar.[0] converts to foo.bar[0]
-//   err(bar).ViaIndex(0).ViaField(foo) -> foo.[0].bar converts to foo[0].bar
-//   err(bar).ViaField(foo).ViaIndex(0) -> [0].foo.bar converts to [0].foo.bar
-//   err(bar).ViaIndex(0).ViaIndex(1).ViaField(foo) -> foo.[1].[0].bar converts to foo[1][0].bar
+//
+//	err([0]).ViaField(bar).ViaField(foo) -> foo.bar.[0] converts to foo.bar[0]
+//	err(bar).ViaIndex(0).ViaField(foo) -> foo.[0].bar converts to foo[0].bar
+//	err(bar).ViaField(foo).ViaIndex(0) -> [0].foo.bar converts to [0].foo.bar
+//	err(bar).ViaIndex(0).ViaIndex(1).ViaField(foo) -> foo.[1].[0].bar converts to foo[1][0].bar
 func flatten(path []string) string {
 	var newPath []string
 	for _, part := range path {
diff --git a/codegen/cmd/injection-gen/args/args.go b/codegen/cmd/injection-gen/args/args.go
index 720477077..b50596846 100644
--- a/codegen/cmd/injection-gen/args/args.go
+++ b/codegen/cmd/injection-gen/args/args.go
@@ -29,6 +29,7 @@ type CustomArgs struct {
 	ExternalVersionsInformersPackage string
 	ListersPackage                   string
 	ForceKinds                       string
+	ListerHasPointerElem             bool
 }
 
 // NewDefaults returns default arguments for the generator.
@@ -45,6 +46,7 @@ func (ca *CustomArgs) AddFlags(fs *pflag.FlagSet) {
 	fs.StringVar(&ca.ExternalVersionsInformersPackage, "external-versions-informers-package", ca.ExternalVersionsInformersPackage, "the full package name for the external versions injection informer to use")
 	fs.StringVar(&ca.ListersPackage, "listers-package", ca.ListersPackage, "the full package name for client listers to use")
 	fs.StringVar(&ca.ForceKinds, "force-genreconciler-kinds", ca.ForceKinds, `force kinds will override the genreconciler tag setting for the given set of kinds, comma separated: "Foo,Bar,Baz"`)
+	fs.BoolVar(&ca.ListerHasPointerElem, "lister-has-pointer-elem", ca.ListerHasPointerElem, "set to true if the List types have an Item array of pointer element types")
 }
 
 // Validate checks the given arguments.
diff --git a/codegen/cmd/injection-gen/generators/informer.go b/codegen/cmd/injection-gen/generators/informer.go
index d00158b28..305ec2476 100644
--- a/codegen/cmd/injection-gen/generators/informer.go
+++ b/codegen/cmd/injection-gen/generators/informer.go
@@ -41,6 +41,7 @@ type injectionGenerator struct {
 	injectionClientSetPackage   string
 	clientSetPackage            string
 	listerPkg                   string
+	listerHasPointerElem        bool
 }
 
 var _ generator.Generator = (*injectionGenerator)(nil)
@@ -92,6 +93,7 @@ func (g *injectionGenerator) GenerateType(c *generator.Context, t *types.Type, w
 		"clientSetInterface":               c.Universe.Type(types.Name{Package: g.clientSetPackage, Name: "Interface"}),
 		"resourceLister":                   c.Universe.Type(types.Name{Name: g.typeToGenerate.Name.Name + "Lister", Package: g.listerPkg}),
 		"resourceNamespaceLister":          c.Universe.Type(types.Name{Name: g.typeToGenerate.Name.Name + "NamespaceLister", Package: g.listerPkg}),
+		"listerHasPointerElem":             g.listerHasPointerElem,
 		"groupGoName":                      namer.IC(g.groupGoName),
 		"versionGoName":                    namer.IC(g.groupVersion.Version.String()),
 		"group":                            g.groupVersion.Group.String(),
@@ -226,7 +228,7 @@ func (w *wrapper) List(selector {{ .labelsSelector|raw }}) (ret []*{{ .type|raw
 		return nil, err
 	}
 	for idx := range lo.Items {
-		ret = append(ret, &lo.Items[idx])
+		ret = append(ret, {{if not .listerHasPointerElem}}&{{end}}lo.Items[idx])
 	}
 	return ret, nil
 }
diff --git a/codegen/cmd/injection-gen/generators/packages.go b/codegen/cmd/injection-gen/generators/packages.go
index 62a143d43..72d5ed0d2 100644
--- a/codegen/cmd/injection-gen/generators/packages.go
+++ b/codegen/cmd/injection-gen/generators/packages.go
@@ -446,6 +446,7 @@ func versionInformerPackages(basePackage string, groupPkgName string, gv clientg
 					clientSetPackage:            customArgs.VersionedClientSetPackage,
 					injectionClientSetPackage:   filepath.Join(basePackage, "client"),
 					listerPkg:                   listerPackagePath,
+					listerHasPointerElem:        customArgs.ListerHasPointerElem,
 				})
 				return generators
 			},
diff --git a/go.mod b/go.mod
index 6d6a94222..a50cbadfe 100644
--- a/go.mod
+++ b/go.mod
@@ -50,7 +50,7 @@ require (
 	k8s.io/gengo v0.0.0-20211129171323-c02415ce4185
 	k8s.io/klog/v2 v2.70.2-0.20220707122935-0990e81f1a8f
 	k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed
-	knative.dev/hack v0.0.0-20221010154335-3fdc50b9c24a
+	knative.dev/hack v0.0.0-20221024013916-9d2ae47c16b2
 	sigs.k8s.io/yaml v1.3.0
 )
 
diff --git a/go.sum b/go.sum
index abb32310a..33c73d26e 100644
--- a/go.sum
+++ b/go.sum
@@ -970,8 +970,8 @@ k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 h1:MQ8BAZPZlWk3S9K4a9NCkI
 k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU=
 k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4=
 k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
-knative.dev/hack v0.0.0-20221010154335-3fdc50b9c24a h1:yfq1OMrkyYkxDeM0pmAOeN4YF16R/WG0C+VvLBeq4uc=
-knative.dev/hack v0.0.0-20221010154335-3fdc50b9c24a/go.mod h1:yk2OjGDsbEnQjfxdm0/HJKS2WqTLEFg/N6nUs6Rqx3Q=
+knative.dev/hack v0.0.0-20221024013916-9d2ae47c16b2 h1:DvWcy2c6wvjDo+rPRWe9Rn5QEH8fiq/j4QWOka0wMvQ=
+knative.dev/hack v0.0.0-20221024013916-9d2ae47c16b2/go.mod h1:yk2OjGDsbEnQjfxdm0/HJKS2WqTLEFg/N6nUs6Rqx3Q=
 pgregory.net/rapid v0.3.3 h1:jCjBsY4ln4Atz78QoBWxUEvAHaFyNDQg9+WU62aCn1U=
 pgregory.net/rapid v0.3.3/go.mod h1:UYpPVyjFHzYBGHIxLFoupi8vwk6rXNzRY9OMvVxFIOU=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
diff --git a/metrics/config.go b/metrics/config.go
index 27bbf9206..3fe3c1737 100644
--- a/metrics/config.go
+++ b/metrics/config.go
@@ -124,12 +124,7 @@ func (mc *metricsConfig) record(ctx context.Context, mss []stats.Measurement, ro
 
 func createMetricsConfig(_ context.Context, ops ExporterOptions) (*metricsConfig, error) {
 	var mc metricsConfig
-
-	if ops.Domain == "" {
-		return nil, errors.New("metrics domain cannot be empty")
-	}
 	mc.domain = ops.Domain
-
 	if ops.Component == "" {
 		return nil, errors.New("metrics component name cannot be empty")
 	}
@@ -159,6 +154,9 @@ func createMetricsConfig(_ context.Context, ops ExporterOptions) (*metricsConfig
 
 	switch mc.backendDestination {
 	case openCensus:
+		if ops.Domain == "" {
+			return nil, errors.New("metrics domain cannot be empty")
+		}
 		mc.collectorAddress = ops.ConfigMap[collectorAddressKey]
 		if isSecure := ops.ConfigMap[collectorSecureKey]; isSecure != "" {
 			var err error
@@ -221,22 +219,7 @@ func Domain() string {
 	if domain := os.Getenv(DomainEnv); domain != "" {
 		return domain
 	}
-
-	panic(fmt.Sprintf(`The environment variable %q is not set
-
-If this is a process running on Kubernetes, then it should be specifying
-this via:
-
-  env:
-  - name: %s
-    value: knative.dev/some-repository
-
-If this is a Go unit test consuming metric.Domain() then it should add the
-following import:
-
-import (
-	_ "knative.dev/pkg/metrics/testing"
-)`, DomainEnv, DomainEnv))
+	return ""
 }
 
 // prometheusPort returns the TCP port number configured via the environment
diff --git a/metrics/config_observability.go b/metrics/config_observability.go
index f95d6bc8c..b6affd293 100644
--- a/metrics/config_observability.go
+++ b/metrics/config_observability.go
@@ -110,6 +110,9 @@ func defaultConfig() *ObservabilityConfig {
 // NewObservabilityConfigFromConfigMap creates a ObservabilityConfig from the supplied ConfigMap
 func NewObservabilityConfigFromConfigMap(configMap *corev1.ConfigMap) (*ObservabilityConfig, error) {
 	oc := defaultConfig()
+	if configMap == nil {
+		return oc, nil
+	}
 
 	if err := cm.Parse(configMap.Data,
 		cm.AsBool("logging.enable-var-log-collection", &oc.EnableVarLogCollection),
diff --git a/vendor/modules.txt b/vendor/modules.txt
index b8b745d9e..7f8dc8410 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -1116,7 +1116,7 @@ k8s.io/utils/net
 k8s.io/utils/pointer
 k8s.io/utils/strings/slices
 k8s.io/utils/trace
-# knative.dev/hack v0.0.0-20221010154335-3fdc50b9c24a
+# knative.dev/hack v0.0.0-20221024013916-9d2ae47c16b2
 ## explicit; go 1.18
 knative.dev/hack
 # sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2
diff --git a/webhook/resourcesemantics/validation/validation_admit.go b/webhook/resourcesemantics/validation/validation_admit.go
index 2b05a7cd0..d30e1e981 100644
--- a/webhook/resourcesemantics/validation/validation_admit.go
+++ b/webhook/resourcesemantics/validation/validation_admit.go
@@ -84,7 +84,10 @@ func (ac *reconciler) Admit(ctx context.Context, request *admissionv1.AdmissionR
 		// below all overwrite `resp`, but the `defer` affords us one final
 		// crack at things.
 		defer func() {
-			resp.Warnings = []string{warnings.Error()}
+			resp.Warnings = make([]string, 0, len(warnings))
+			for _, w := range warnings {
+				resp.Warnings = append(resp.Warnings, w.Error())
+			}
 		}()
 	}
 	if errors != nil {
@@ -157,7 +160,7 @@ func (ac *reconciler) decodeRequestAndPrepareContext(
 	return ctx, newObj, nil
 }
 
-func validate(ctx context.Context, resource resourcesemantics.GenericCRD, req *admissionv1.AdmissionRequest) (err error, warn error) {
+func validate(ctx context.Context, resource resourcesemantics.GenericCRD, req *admissionv1.AdmissionRequest) (err error, warn []error) {
 	logger := logging.FromContext(ctx)
 
 	// Only run validation for supported create and update validation.
@@ -187,7 +190,11 @@ func validate(ctx context.Context, resource resourcesemantics.GenericCRD, req *a
 			err = errorResult
 		}
 		if warningResult := result.Filter(apis.WarningLevel); warningResult != nil {
-			warn = warningResult
+			ws := warningResult.WrappedErrors()
+			warn = make([]error, 0, len(ws))
+			for _, w := range ws {
+				warn = append(warn, w)
+			}
 		}
 	}
 	return err, warn


Test patch:
diff --git a/apis/field_error_test.go b/apis/field_error_test.go
index da376d2f1..a52fb9e6a 100644
--- a/apis/field_error_test.go
+++ b/apis/field_error_test.go
@@ -23,6 +23,7 @@ import (
 	"time"
 
 	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
 )
 
 type testStruct struct {
@@ -361,6 +362,38 @@ missing field(s): bar`,
 	}
 }
 
+func TestWrappedErrors(t *testing.T) {
+	for _, test := range []struct {
+		name string
+		err  *FieldError
+		want []*FieldError
+	}{{
+		name: "nil",
+		err:  nil,
+		want: nil,
+	}, {
+		name: "empty",
+		err:  &FieldError{},
+		want: nil,
+	}, {
+		name: "has errors",
+		err: (&FieldError{}).Also(
+			ErrInvalidValue("42", "answer"),
+			ErrGeneric("generic", "error"),
+		),
+		want: []*FieldError{
+			ErrGeneric("generic", "error"),
+			ErrInvalidValue("42", "answer"),
+		},
+	}} {
+		t.Run(test.name, func(t *testing.T) {
+			if diff := cmp.Diff(test.want, test.err.WrappedErrors(), cmp.AllowUnexported(FieldError{}), cmpopts.EquateEmpty()); diff != "" {
+				t.Errorf("unexpected GetErrors() -want,+got: %s", diff)
+			}
+		})
+	}
+}
+
 func TestViaIndexOrKeyFieldError(t *testing.T) {
 	tests := []struct {
 		name     string
diff --git a/injection/sharedmain/main_test.go b/injection/sharedmain/main_test.go
index e32a53f15..bc9b54ce6 100644
--- a/injection/sharedmain/main_test.go
+++ b/injection/sharedmain/main_test.go
@@ -105,7 +105,7 @@ func TestWithLeaderElectionConfig(t *testing.T) {
 	}
 }
 
-func TextWithObservabilityConfig(t *testing.T) {
+func TestWithObservabilityConfig(t *testing.T) {
 	want := &metrics.ObservabilityConfig{
 		EnableVarLogCollection:  false,
 		LoggingURLTemplate:      "url-template",
diff --git a/metrics/config_test.go b/metrics/config_test.go
index ce7d0a733..7b77131db 100644
--- a/metrics/config_test.go
+++ b/metrics/config_test.go
@@ -61,7 +61,7 @@ var (
 		name: "emptyDomain",
 		ops: ExporterOptions{
 			ConfigMap: map[string]string{
-				BackendDestinationKey: string(prometheus),
+				BackendDestinationKey: string(openCensus),
 			},
 			Component: testComponent,
 		},
diff --git a/reconciler/testing/reactions.go b/reconciler/testing/reactions.go
index d66b41a71..8b86f1067 100644
--- a/reconciler/testing/reactions.go
+++ b/reconciler/testing/reactions.go
@@ -52,7 +52,8 @@ func ValidateCreates(ctx context.Context, action clientgotesting.Action) (handle
 	if !ok {
 		return false, nil, nil
 	}
-	if err := obj.Validate(ctx); err != nil {
+	// Only return error-level errors; warnings should not block API calls.
+	if err := obj.Validate(ctx).Filter(apis.ErrorLevel); err != nil {
 		return true, nil, err
 	}
 	return false, nil, nil
@@ -64,7 +65,8 @@ func ValidateUpdates(ctx context.Context, action clientgotesting.Action) (handle
 	if !ok {
 		return false, nil, nil
 	}
-	if err := obj.Validate(ctx); err != nil {
+	// Only return error-level errors; warnings should not block API calls.
+	if err := obj.Validate(ctx).Filter(apis.ErrorLevel); err != nil {
 		return true, nil, err
 	}
 	return false, nil, nil
diff --git a/vendor/knative.dev/hack/presubmit-tests.sh b/vendor/knative.dev/hack/presubmit-tests.sh
index 1277aed4a..fd2a2c069 100644
--- a/vendor/knative.dev/hack/presubmit-tests.sh
+++ b/vendor/knative.dev/hack/presubmit-tests.sh
@@ -139,14 +139,7 @@ function __build_test_runner_for_module() {
   # Don't merge these two lines, or return code will always be 0.
   # Get all build tags in go code (ignore /vendor, /hack and /third_party)
   local tags
-  tags="$(grep -I -r '// +build' . | grep -v '/vendor/' | \
-    grep -v '/hack/' | \
-    grep -v '/third_party' | \
-    cut -f3 -d' ' | \
-    tr ',' '\n' | \
-    sort | uniq | \
-    grep -v '^!' | \
-    paste -s -d, /dev/stdin)"
+  tags="$(go run knative.dev/test-infra/tools/go-ls-tags@latest --joiner=,)"
   local go_pkg_dirs
   go_pkg_dirs="$(go list -tags "${tags}" ./...)" || return $?
   if [[ -z "${go_pkg_dirs}" ]]; then