Repository: letsencrypt/boulder

Issue:
Teach the RA to reject ipAddress orders unless they specify the short-lived profile


Discussion:

Solution patch:
diff --git a/.golangci.yml b/.golangci.yml
index e99a71e83..a14e9f6df 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -1,6 +1,8 @@
 linters:
   disable-all: true
   enable:
+    - asciicheck
+    - bidichk
     - errcheck
     - gofmt
     - gosec
@@ -9,6 +11,7 @@ linters:
     - ineffassign
     - misspell
     - nolintlint
+    - spancheck
     - sqlclosecheck
     - typecheck
     - unconvert
diff --git a/bdns/dns.go b/bdns/dns.go
index 1ad08f440..ec502fe64 100644
--- a/bdns/dns.go
+++ b/bdns/dns.go
@@ -391,7 +391,6 @@ func (dnsClient *impl) exchangeOne(ctx context.Context, hostname string, qtype u
 			return
 		}
 	}
-
 }
 
 // isTLD returns a simplified view of whether something is a TLD: does it have
diff --git a/cmd/boulder-ca/main.go b/cmd/boulder-ca/main.go
index e579a2ba6..156b4fe90 100644
--- a/cmd/boulder-ca/main.go
+++ b/cmd/boulder-ca/main.go
@@ -164,8 +164,9 @@ func main() {
 	metrics := ca.NewCAMetrics(scope)
 
 	cmd.FailOnError(c.PA.CheckChallenges(), "Invalid PA configuration")
+	cmd.FailOnError(c.PA.CheckIdentifiers(), "Invalid PA configuration")
 
-	pa, err := policy.New(c.PA.Challenges, logger)
+	pa, err := policy.New(c.PA.Identifiers, c.PA.Challenges, logger)
 	cmd.FailOnError(err, "Couldn't create PA")
 
 	if c.CA.HostnamePolicyFile == "" {
diff --git a/cmd/boulder-ra/main.go b/cmd/boulder-ra/main.go
index 2025de998..56dd110f3 100644
--- a/cmd/boulder-ra/main.go
+++ b/cmd/boulder-ra/main.go
@@ -166,8 +166,9 @@ func main() {
 
 	// Validate PA config and set defaults if needed
 	cmd.FailOnError(c.PA.CheckChallenges(), "Invalid PA configuration")
+	cmd.FailOnError(c.PA.CheckIdentifiers(), "Invalid PA configuration")
 
-	pa, err := policy.New(c.PA.Challenges, logger)
+	pa, err := policy.New(c.PA.Identifiers, c.PA.Challenges, logger)
 	cmd.FailOnError(err, "Couldn't create PA")
 
 	if c.RA.HostnamePolicyFile == "" {
diff --git a/cmd/cert-checker/main.go b/cmd/cert-checker/main.go
index 0d78c3f7a..974308ab9 100644
--- a/cmd/cert-checker/main.go
+++ b/cmd/cert-checker/main.go
@@ -562,6 +562,7 @@ func main() {
 
 	// Validate PA config and set defaults if needed.
 	cmd.FailOnError(config.PA.CheckChallenges(), "Invalid PA configuration")
+	cmd.FailOnError(config.PA.CheckIdentifiers(), "Invalid PA configuration")
 
 	kp, err := sagoodkey.NewPolicy(&config.CertChecker.GoodKey, nil)
 	cmd.FailOnError(err, "Unable to create key policy")
@@ -575,7 +576,7 @@ func main() {
 	})
 	prometheus.DefaultRegisterer.MustRegister(checkerLatency)
 
-	pa, err := policy.New(config.PA.Challenges, logger)
+	pa, err := policy.New(config.PA.Identifiers, config.PA.Challenges, logger)
 	cmd.FailOnError(err, "Failed to create PA")
 
 	err = pa.LoadHostnamePolicyFile(config.CertChecker.HostnamePolicyFile)
diff --git a/cmd/config.go b/cmd/config.go
index 3072f206c..f8b6b847f 100644
--- a/cmd/config.go
+++ b/cmd/config.go
@@ -16,6 +16,7 @@ import (
 
 	"github.com/letsencrypt/boulder/config"
 	"github.com/letsencrypt/boulder/core"
+	"github.com/letsencrypt/boulder/identifier"
 )
 
 // PasswordConfig contains a path to a file containing a password.
@@ -99,8 +100,9 @@ type SMTPConfig struct {
 // database, what policies it should enforce, and what challenges
 // it should offer.
 type PAConfig struct {
-	DBConfig   `validate:"-"`
-	Challenges map[core.AcmeChallenge]bool `validate:"omitempty,dive,keys,oneof=http-01 dns-01 tls-alpn-01,endkeys"`
+	DBConfig    `validate:"-"`
+	Challenges  map[core.AcmeChallenge]bool        `validate:"omitempty,dive,keys,oneof=http-01 dns-01 tls-alpn-01,endkeys"`
+	Identifiers map[identifier.IdentifierType]bool `validate:"omitempty,dive,keys,oneof=dns ip,endkeys"`
 }
 
 // CheckChallenges checks whether the list of challenges in the PA config
@@ -117,6 +119,17 @@ func (pc PAConfig) CheckChallenges() error {
 	return nil
 }
 
+// CheckIdentifiers checks whether the list of identifiers in the PA config
+// actually contains valid identifier type names
+func (pc PAConfig) CheckIdentifiers() error {
+	for i := range pc.Identifiers {
+		if !i.IsValid() {
+			return fmt.Errorf("invalid identifier type in PA config: %s", i)
+		}
+	}
+	return nil
+}
+
 // HostnamePolicyConfig specifies a file from which to load a policy regarding
 // what hostnames to issue for.
 type HostnamePolicyConfig struct {
diff --git a/cmd/reversed-hostname-checker/main.go b/cmd/reversed-hostname-checker/main.go
index 1e60f618a..9d7a33f37 100644
--- a/cmd/reversed-hostname-checker/main.go
+++ b/cmd/reversed-hostname-checker/main.go
@@ -40,7 +40,7 @@ func main() {
 	scanner := bufio.NewScanner(input)
 	logger := cmd.NewLogger(cmd.SyslogConfig{StdoutLevel: 7})
 	logger.Info(cmd.VersionString())
-	pa, err := policy.New(nil, logger)
+	pa, err := policy.New(nil, nil, logger)
 	if err != nil {
 		log.Fatal(err)
 	}
diff --git a/ctpolicy/ctconfig/ctconfig.go b/ctpolicy/ctconfig/ctconfig.go
index 8adab4adb..2e936ffae 100644
--- a/ctpolicy/ctconfig/ctconfig.go
+++ b/ctpolicy/ctconfig/ctconfig.go
@@ -1,93 +1,9 @@
 package ctconfig
 
 import (
-	"errors"
-	"fmt"
-	"time"
-
 	"github.com/letsencrypt/boulder/config"
 )
 
-// LogShard describes a single shard of a temporally sharded
-// CT log
-type LogShard struct {
-	URI         string
-	Key         string
-	WindowStart time.Time
-	WindowEnd   time.Time
-}
-
-// TemporalSet contains a set of temporal shards of a single log
-type TemporalSet struct {
-	Name   string
-	Shards []LogShard
-}
-
-// Setup initializes the TemporalSet by parsing the start and end dates
-// and verifying WindowEnd > WindowStart
-func (ts *TemporalSet) Setup() error {
-	if ts.Name == "" {
-		return errors.New("Name cannot be empty")
-	}
-	if len(ts.Shards) == 0 {
-		return errors.New("temporal set contains no shards")
-	}
-	for i := range ts.Shards {
-		if !ts.Shards[i].WindowEnd.After(ts.Shards[i].WindowStart) {
-			return errors.New("WindowStart must be before WindowEnd")
-		}
-	}
-	return nil
-}
-
-// pick chooses the correct shard from a TemporalSet to use for the given
-// expiration time. In the case where two shards have overlapping windows
-// the earlier of the two shards will be chosen.
-func (ts *TemporalSet) pick(exp time.Time) (*LogShard, error) {
-	for _, shard := range ts.Shards {
-		if exp.Before(shard.WindowStart) {
-			continue
-		}
-		if !exp.Before(shard.WindowEnd) {
-			continue
-		}
-		return &shard, nil
-	}
-	return nil, fmt.Errorf("no valid shard available for temporal set %q for expiration date %q", ts.Name, exp)
-}
-
-// LogDescription contains the information needed to submit certificates
-// to a CT log and verify returned receipts. If TemporalSet is non-nil then
-// URI and Key should be empty.
-type LogDescription struct {
-	URI             string
-	Key             string
-	SubmitFinalCert bool
-
-	*TemporalSet
-}
-
-// Info returns the URI and key of the log, either from a plain log description
-// or from the earliest valid shard from a temporal log set
-func (ld LogDescription) Info(exp time.Time) (string, string, error) {
-	if ld.TemporalSet == nil {
-		return ld.URI, ld.Key, nil
-	}
-	shard, err := ld.TemporalSet.pick(exp)
-	if err != nil {
-		return "", "", err
-	}
-	return shard.URI, shard.Key, nil
-}
-
-// CTGroup represents a group of CT Logs. Although capable of holding logs
-// grouped by any arbitrary feature, is today primarily used to hold logs which
-// are all operated by the same legal entity.
-type CTGroup struct {
-	Name string
-	Logs []LogDescription
-}
-
 // CTConfig is the top-level config object expected to be embedded in an
 // executable's JSON config struct.
 type CTConfig struct {
@@ -109,13 +25,3 @@ type CTConfig struct {
 	// and final certs to the same log.
 	FinalLogs []string
 }
-
-// LogID holds enough information to uniquely identify a CT Log: its log_id
-// (the base64-encoding of the SHA-256 hash of its public key) and its human-
-// readable name/description. This is used to extract other log parameters
-// (such as its URL and public key) from the Chrome Log List.
-type LogID struct {
-	Name        string
-	ID          string
-	SubmitFinal bool
-}
diff --git a/email/exporter.go b/email/exporter.go
index c054c836c..58c6d4022 100644
--- a/email/exporter.go
+++ b/email/exporter.go
@@ -41,6 +41,7 @@ type ExporterImpl struct {
 	limiter               *rate.Limiter
 	client                PardotClient
 	emailsHandledCounter  prometheus.Counter
+	pardotErrorCounter    prometheus.Counter
 	log                   blog.Logger
 }
 
@@ -62,12 +63,19 @@ func NewExporterImpl(client PardotClient, perDayLimit float64, maxConcurrentRequ
 	})
 	scope.MustRegister(emailsHandledCounter)
 
+	pardotErrorCounter := prometheus.NewCounter(prometheus.CounterOpts{
+		Name: "email_exporter_errors",
+		Help: "Total number of Pardot API errors encountered by the email exporter",
+	})
+	scope.MustRegister(pardotErrorCounter)
+
 	impl := &ExporterImpl{
 		maxConcurrentRequests: maxConcurrentRequests,
 		limiter:               limiter,
 		toSend:                make([]string, 0, contactsQueueCap),
 		client:                client,
 		emailsHandledCounter:  emailsHandledCounter,
+		pardotErrorCounter:    pardotErrorCounter,
 		log:                   logger,
 	}
 	impl.wake = sync.NewCond(&impl.Mutex)
@@ -145,6 +153,7 @@ func (impl *ExporterImpl) Start(daemonCtx context.Context) {
 
 			err = impl.client.SendContact(email)
 			if err != nil {
+				impl.pardotErrorCounter.Inc()
 				impl.log.Errf("Sending Contact to Pardot: %s", err)
 			}
 			impl.emailsHandledCounter.Inc()
diff --git a/features/features.go b/features/features.go
index 7a27b7ef9..32f2267d1 100644
--- a/features/features.go
+++ b/features/features.go
@@ -23,6 +23,7 @@ type Config struct {
 	InsertAuthzsIndividually    bool
 	EnforceMultiCAA             bool
 	EnforceMPIC                 bool
+	MPICFullResults             bool
 	UnsplitIssuance             bool
 
 	// ServeRenewalInfo exposes the renewalInfo endpoint in the directory and for
@@ -81,11 +82,6 @@ type Config struct {
 	// removing pending authz reuse.
 	NoPendingAuthzReuse bool
 
-	// MPICFullResults causes the VA to wait for all remote (MPIC) results, rather
-	// than cancelling outstanding requests after enough successes or failures for
-	// the result to be determined.
-	MPICFullResults bool
-
 	// StoreARIReplacesInOrders causes the SA to store and retrieve the optional
 	// ARI replaces field in the orders table.
 	StoreARIReplacesInOrders bool
diff --git a/go.mod b/go.mod
index fbf31ed37..acf864e32 100644
--- a/go.mod
+++ b/go.mod
@@ -12,7 +12,7 @@ require (
 	github.com/go-logr/stdr v1.2.2
 	github.com/go-sql-driver/mysql v1.9.1
 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
-	github.com/google/certificate-transparency-go v1.3.2-0.20250506133818-bc7acd89f703
+	github.com/google/certificate-transparency-go v1.3.2-0.20250507091337-0eddb39e94f8
 	github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1
 	github.com/jmhodges/clock v1.2.0
 	github.com/letsencrypt/borp v0.0.0-20240620175310-a78493c6e2bd
@@ -91,5 +91,4 @@ require (
 	google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
 	gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
-	k8s.io/klog/v2 v2.130.1 // indirect
 )
diff --git a/go.sum b/go.sum
index 0ffc689c7..8ce5c6916 100644
--- a/go.sum
+++ b/go.sum
@@ -116,8 +116,8 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
 github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
 github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/certificate-transparency-go v1.3.2-0.20250506133818-bc7acd89f703 h1:3+otaJ9LIkCSdZfq1dF5ewMX+RnmVj6OHXX/KwKfpbI=
-github.com/google/certificate-transparency-go v1.3.2-0.20250506133818-bc7acd89f703/go.mod h1:6Rm5w0Mlv87LyBNOCgfKYjdIBBpF42XpXGsbQvQGomQ=
+github.com/google/certificate-transparency-go v1.3.2-0.20250507091337-0eddb39e94f8 h1:1RSWsOSxq2gk4pD/63bhsPwoOXgz2yXVadxXPbwZ0ec=
+github.com/google/certificate-transparency-go v1.3.2-0.20250507091337-0eddb39e94f8/go.mod h1:6Rm5w0Mlv87LyBNOCgfKYjdIBBpF42XpXGsbQvQGomQ=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -474,5 +474,3 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
-k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
diff --git a/grpc/pb-marshalling.go b/grpc/pb-marshalling.go
index a05c83716..505ebeb91 100644
--- a/grpc/pb-marshalling.go
+++ b/grpc/pb-marshalling.go
@@ -18,7 +18,6 @@ import (
 	corepb "github.com/letsencrypt/boulder/core/proto"
 	"github.com/letsencrypt/boulder/identifier"
 	"github.com/letsencrypt/boulder/probs"
-	"github.com/letsencrypt/boulder/revocation"
 	sapb "github.com/letsencrypt/boulder/sa/proto"
 	vapb "github.com/letsencrypt/boulder/va/proto"
 )
@@ -343,34 +342,6 @@ func newOrderValid(order *corepb.Order) bool {
 	return !(order.RegistrationID == 0 || order.Expires == nil || len(order.Identifiers) == 0)
 }
 
-func CertStatusToPB(certStatus core.CertificateStatus) *corepb.CertificateStatus {
-	return &corepb.CertificateStatus{
-		Serial:                certStatus.Serial,
-		Status:                string(certStatus.Status),
-		OcspLastUpdated:       timestamppb.New(certStatus.OCSPLastUpdated),
-		RevokedDate:           timestamppb.New(certStatus.RevokedDate),
-		RevokedReason:         int64(certStatus.RevokedReason),
-		LastExpirationNagSent: timestamppb.New(certStatus.LastExpirationNagSent),
-		NotAfter:              timestamppb.New(certStatus.NotAfter),
-		IsExpired:             certStatus.IsExpired,
-		IssuerID:              certStatus.IssuerNameID,
-	}
-}
-
-func PBToCertStatus(pb *corepb.CertificateStatus) core.CertificateStatus {
-	return core.CertificateStatus{
-		Serial:                pb.Serial,
-		Status:                core.OCSPStatus(pb.Status),
-		OCSPLastUpdated:       pb.OcspLastUpdated.AsTime(),
-		RevokedDate:           pb.RevokedDate.AsTime(),
-		RevokedReason:         revocation.Reason(pb.RevokedReason),
-		LastExpirationNagSent: pb.LastExpirationNagSent.AsTime(),
-		NotAfter:              pb.NotAfter.AsTime(),
-		IsExpired:             pb.IsExpired,
-		IssuerNameID:          pb.IssuerID,
-	}
-}
-
 // PBToAuthzMap converts a protobuf map of domains mapped to protobuf authorizations to a
 // golang map[string]*core.Authorization.
 func PBToAuthzMap(pb *sapb.Authorizations) (map[identifier.ACMEIdentifier]*core.Authorization, error) {
diff --git a/identifier/identifier.go b/identifier/identifier.go
index 6c9a86f18..e6ed65e1c 100644
--- a/identifier/identifier.go
+++ b/identifier/identifier.go
@@ -30,6 +30,16 @@ const (
 	TypeIP = IdentifierType("ip")
 )
 
+// IsValid tests whether the identifier type is known
+func (i IdentifierType) IsValid() bool {
+	switch i {
+	case TypeDNS, TypeIP:
+		return true
+	default:
+		return false
+	}
+}
+
 // ACMEIdentifier is a struct encoding an identifier that can be validated. The
 // protocol allows for different types of identifier to be supported (DNS
 // names, IP addresses, etc.), but currently we only support RFC 8555 DNS type
diff --git a/mocks/sa.go b/mocks/sa.go
index dd50ebe66..a982a8047 100644
--- a/mocks/sa.go
+++ b/mocks/sa.go
@@ -220,7 +220,6 @@ func (sa *StorageAuthorityReadOnly) GetCertificateStatus(_ context.Context, req
 
 func (sa *StorageAuthorityReadOnly) SetCertificateStatusReady(ctx context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*emptypb.Empty, error) {
 	return nil, status.Error(codes.Unimplemented, "unimplemented mock")
-
 }
 
 // GetRevocationStatus is a mock
diff --git a/policy/pa.go b/policy/pa.go
index cf9e619a0..661a6b6bc 100644
--- a/policy/pa.go
+++ b/policy/pa.go
@@ -16,6 +16,7 @@ import (
 	"golang.org/x/net/idna"
 	"golang.org/x/text/unicode/norm"
 
+	"github.com/letsencrypt/boulder/bdns"
 	"github.com/letsencrypt/boulder/core"
 	berrors "github.com/letsencrypt/boulder/errors"
 	"github.com/letsencrypt/boulder/iana"
@@ -33,14 +34,24 @@ type AuthorityImpl struct {
 	wildcardExactBlocklist map[string]bool
 	blocklistMu            sync.RWMutex
 
-	enabledChallenges map[core.AcmeChallenge]bool
+	enabledChallenges  map[core.AcmeChallenge]bool
+	enabledIdentifiers map[identifier.IdentifierType]bool
 }
 
 // New constructs a Policy Authority.
-func New(challengeTypes map[core.AcmeChallenge]bool, log blog.Logger) (*AuthorityImpl, error) {
+func New(identifierTypes map[identifier.IdentifierType]bool, challengeTypes map[core.AcmeChallenge]bool, log blog.Logger) (*AuthorityImpl, error) {
+	// If identifierTypes are not configured (i.e. nil), default to allowing DNS
+	// identifiers. This default is temporary, to improve deployability.
+	//
+	// TODO(#8184): Remove this default.
+	if identifierTypes == nil {
+		identifierTypes = map[identifier.IdentifierType]bool{identifier.TypeDNS: true}
+	}
+
 	return &AuthorityImpl{
-		log:               log,
-		enabledChallenges: challengeTypes,
+		log:                log,
+		enabledChallenges:  challengeTypes,
+		enabledIdentifiers: identifierTypes,
 	}, nil
 }
 
@@ -167,9 +178,11 @@ var (
 	errPolicyForbidden      = berrors.RejectedIdentifierError("The ACME server refuses to issue a certificate for this domain name, because it is forbidden by policy")
 	errInvalidDNSCharacter  = berrors.MalformedError("Domain name contains an invalid character")
 	errNameTooLong          = berrors.MalformedError("Domain name is longer than 253 bytes")
-	errIPAddress            = berrors.MalformedError("The ACME server can not issue a certificate for an IP address")
+	errIPAddressInDNS       = berrors.MalformedError("Identifier type is DNS but value is an IP address")
+	errIPInvalid            = berrors.MalformedError("IP address is invalid")
+	errIPSpecialPurpose     = berrors.MalformedError("IP address is in a special-purpose address block")
 	errTooManyLabels        = berrors.MalformedError("Domain name has more than 10 labels (parts)")
-	errEmptyName            = berrors.MalformedError("Domain name is empty")
+	errEmptyIdentifier      = berrors.MalformedError("Identifier value (name) is empty")
 	errNameEndsInDot        = berrors.MalformedError("Domain name ends in a dot")
 	errTooFewLabels         = berrors.MalformedError("Domain name needs at least one dot")
 	errLabelTooShort        = berrors.MalformedError("Domain name can not have two dots in a row")
@@ -180,7 +193,7 @@ var (
 	errMalformedWildcard    = berrors.MalformedError("Domain name contains an invalid wildcard. A wildcard is only permitted before the first dot in a domain name")
 	errICANNTLDWildcard     = berrors.MalformedError("Domain name is a wildcard for an ICANN TLD")
 	errWildcardNotSupported = berrors.MalformedError("Wildcard domain names are not supported")
-	errUnsupportedIdent     = berrors.MalformedError("invalid non-DNS type identifier")
+	errUnsupportedIdent     = berrors.MalformedError("Invalid identifier type")
 )
 
 // validNonWildcardDomain checks that a domain isn't:
@@ -198,7 +211,7 @@ var (
 // It does NOT ensure that the domain is absent from any PA blocked lists.
 func validNonWildcardDomain(domain string) error {
 	if domain == "" {
-		return errEmptyName
+		return errEmptyIdentifier
 	}
 
 	if strings.HasPrefix(domain, "*.") {
@@ -216,7 +229,7 @@ func validNonWildcardDomain(domain string) error {
 	}
 
 	if ip := net.ParseIP(domain); ip != nil {
-		return errIPAddress
+		return errIPAddressInDNS
 	}
 
 	if strings.HasSuffix(domain, ".") {
@@ -319,6 +332,34 @@ func ValidDomain(domain string) error {
 	return validNonWildcardDomain(baseDomain)
 }
 
+// validIP checks that an IP address:
+//   - isn't empty
+//   - is an IPv4 or IPv6 address
+//   - isn't in an IANA special-purpose address registry
+//
+// It does NOT ensure that the IP address is absent from any PA blocked lists.
+func validIP(ip string) error {
+	if ip == "" {
+		return errEmptyIdentifier
+	}
+
+	// Check the output of net.IP.String(), to ensure the input complied with
+	// RFC 8738, Sec. 3. ("The identifier value MUST contain the textual form of
+	// the address as defined in RFC 1123, Sec. 2.1 for IPv4 and in RFC 5952,
+	// Sec. 4 for IPv6.") ParseIP() will accept a non-compliant but otherwise
+	// valid string; String() will output a compliant string.
+	parsedIP := net.ParseIP(ip)
+	if parsedIP == nil || parsedIP.String() != ip {
+		return errIPInvalid
+	}
+
+	if bdns.IsReservedIP(parsedIP) {
+		return errIPSpecialPurpose
+	}
+
+	return nil
+}
+
 // forbiddenMailDomains is a map of domain names we do not allow after the
 // @ symbol in contact mailto addresses. These are frequently used when
 // copy-pasting example configurations and would not result in expiration
@@ -390,42 +431,50 @@ func (pa *AuthorityImpl) WillingToIssue(idents identifier.ACMEIdentifiers) error
 
 	var subErrors []berrors.SubBoulderError
 	for _, ident := range idents {
-		if ident.Type != identifier.TypeDNS {
-			subErrors = append(subErrors, subError(ident, errUnsupportedIdent))
+		if !pa.IdentifierTypeEnabled(ident.Type) {
+			subErrors = append(subErrors, subError(ident, berrors.RejectedIdentifierError("The ACME server has disabled this identifier type")))
 			continue
 		}
 
-		if strings.Count(ident.Value, "*") > 0 {
-			// The base domain is the wildcard request with the `*.` prefix removed
-			baseDomain := strings.TrimPrefix(ident.Value, "*.")
+		// Only DNS identifiers are subject to wildcard and blocklist checks.
+		// Unsupported identifier types will have been caught by
+		// WellFormedIdentifiers().
+		//
+		// TODO(#7311): We may want to implement IP address blocklists too.
+		if ident.Type == identifier.TypeDNS {
+			if strings.Count(ident.Value, "*") > 0 {
+				// The base domain is the wildcard request with the `*.` prefix removed
+				baseDomain := strings.TrimPrefix(ident.Value, "*.")
+
+				// The base domain can't be in the wildcard exact blocklist
+				err = pa.checkWildcardHostList(baseDomain)
+				if err != nil {
+					subErrors = append(subErrors, subError(ident, err))
+					continue
+				}
+			}
 
-			// The base domain can't be in the wildcard exact blocklist
-			err = pa.checkWildcardHostList(baseDomain)
+			// For both wildcard and non-wildcard domains, check whether any parent domain
+			// name is on the regular blocklist.
+			err := pa.checkHostLists(ident.Value)
 			if err != nil {
 				subErrors = append(subErrors, subError(ident, err))
 				continue
 			}
 		}
-
-		// For both wildcard and non-wildcard domains, check whether any parent domain
-		// name is on the regular blocklist.
-		err := pa.checkHostLists(ident.Value)
-		if err != nil {
-			subErrors = append(subErrors, subError(ident, err))
-			continue
-		}
 	}
 	return combineSubErrors(subErrors)
 }
 
-// WellFormedIdentifiers returns an error if any of the provided domains do not
-// meet these criteria:
+// WellFormedIdentifiers returns an error if any of the provided identifiers do
+// not meet these criteria:
 //
+// For DNS identifiers:
 //   - MUST contains only lowercase characters, numbers, hyphens, and dots
 //   - MUST NOT have more than maxLabels labels
 //   - MUST follow the DNS hostname syntax rules in RFC 1035 and RFC 2181
 //
-// In particular, it:
+// In particular, DNS identifiers:
 //   - MUST NOT contain underscores
 //   - MUST NOT match the syntax of an IP address
 //   - MUST end in a public suffix
@@ -433,25 +482,33 @@ func (pa *AuthorityImpl) WillingToIssue(idents identifier.ACMEIdentifiers) error
 //   - MUST NOT be a label-wise suffix match for a name on the block list,
 //     where comparison is case-independent (normalized to lower case)
 //
-// If a domain contains a *, we additionally require:
+// If a DNS identifier contains a *, we additionally require:
 //   - There is at most one `*` wildcard character
 //   - That the wildcard character is the leftmost label
 //   - That the wildcard label is not immediately adjacent to a top level ICANN
 //     TLD
 //
-// If multiple domains are invalid, the error will contain suberrors specific to
-// each domain.
+// For IP identifiers:
+//   - MUST match the syntax of an IP address
+//   - MUST NOT be in an IANA special-purpose address registry
+//
+// If multiple identifiers are invalid, the error will contain suberrors
+// specific to each identifier.
 func WellFormedIdentifiers(idents identifier.ACMEIdentifiers) error {
 	var subErrors []berrors.SubBoulderError
 	for _, ident := range idents {
-		// TODO(#7311): When this gets a third case for TypeIP, this will be
-		// more elegant as a switch/case.
-		if ident.Type == identifier.TypeDNS {
+		switch ident.Type {
+		case identifier.TypeDNS:
 			err := ValidDomain(ident.Value)
 			if err != nil {
 				subErrors = append(subErrors, subError(ident, err))
 			}
-		} else {
+		case identifier.TypeIP:
+			err := validIP(ident.Value)
+			if err != nil {
+				subErrors = append(subErrors, subError(ident, err))
+			}
+		default:
 			subErrors = append(subErrors, subError(ident, errUnsupportedIdent))
 		}
 	}
@@ -530,26 +587,34 @@ func (pa *AuthorityImpl) checkHostLists(domain string) error {
 // filtering can happen dynamically at request rather than being set in stone
 // at creation time.
 func (pa *AuthorityImpl) ChallengeTypesFor(ident identifier.ACMEIdentifier) ([]core.AcmeChallenge, error) {
-	// If the identifier is for a DNS wildcard name we only provide a DNS-01
-	// challenge, to comply with the BRs Sections 3.2.2.4.19 and 3.2.2.4.20
-	// stating that ACME HTTP-01 and TLS-ALPN-01 are not suitable for validating
-	// Wildcard Domains.
-	if ident.Type == identifier.TypeDNS && strings.HasPrefix(ident.Value, "*.") {
-		return []core.AcmeChallenge{core.ChallengeTypeDNS01}, nil
-	}
+	switch ident.Type {
+	case identifier.TypeDNS:
+		// If the identifier is for a DNS wildcard name we only provide a DNS-01
+		// challenge, to comply with the BRs Sections 3.2.2.4.19 and 3.2.2.4.20
+		// stating that ACME HTTP-01 and TLS-ALPN-01 are not suitable for validating
+		// Wildcard Domains.
+		if strings.HasPrefix(ident.Value, "*.") {
+			return []core.AcmeChallenge{core.ChallengeTypeDNS01}, nil
+		}
 
-	// Return all challenge types we support for non-wildcard DNS identifiers.
-	if ident.Type == identifier.TypeDNS {
+		// Return all challenge types we support for non-wildcard DNS identifiers.
 		return []core.AcmeChallenge{
 			core.ChallengeTypeHTTP01,
 			core.ChallengeTypeDNS01,
 			core.ChallengeTypeTLSALPN01,
 		}, nil
+	case identifier.TypeIP:
+		// Only HTTP-01 and TLS-ALPN-01 are suitable for IP address identifiers
+		// per RFC 8738, Sec. 4.
+		return []core.AcmeChallenge{
+			core.ChallengeTypeHTTP01,
+			core.ChallengeTypeTLSALPN01,
+		}, nil
+	default:
+		// Otherwise return an error because we don't support any challenges for this
+		// identifier type.
+		return nil, fmt.Errorf("unrecognized identifier type %q", ident.Type)
 	}
-
-	// Otherwise return an error because we don't support any challenges for this
-	// identifier type.
-	return nil, fmt.Errorf("unrecognized identifier type %q", ident.Type)
 }
 
 // ChallengeTypeEnabled returns whether the specified challenge type is enabled
@@ -583,3 +648,10 @@ func (pa *AuthorityImpl) CheckAuthzChallenges(authz *core.Authorization) error {
 
 	return nil
 }
+
+// IdentifierTypeEnabled returns whether the specified identifier type is enabled
+func (pa *AuthorityImpl) IdentifierTypeEnabled(t identifier.IdentifierType) bool {
+	pa.blocklistMu.RLock()
+	defer pa.blocklistMu.RUnlock()
+	return pa.enabledIdentifiers[t]
+}
diff --git a/sa/database.go b/sa/database.go
index a0963bce0..34447d7da 100644
--- a/sa/database.go
+++ b/sa/database.go
@@ -270,7 +270,7 @@ func initTables(dbMap *borp.DbMap) {
 	regTable.ColMap("KeySHA256").SetNotNull(true).SetUnique(true)
 	dbMap.AddTableWithName(issuedNameModel{}, "issuedNames").SetKeys(true, "ID")
 	dbMap.AddTableWithName(core.Certificate{}, "certificates").SetKeys(true, "ID")
-	dbMap.AddTableWithName(core.CertificateStatus{}, "certificateStatus").SetKeys(true, "ID")
+	dbMap.AddTableWithName(certificateStatusModel{}, "certificateStatus").SetKeys(true, "ID")
 	dbMap.AddTableWithName(core.FQDNSet{}, "fqdnSets").SetKeys(true, "ID")
 	tableMap := dbMap.AddTableWithName(orderModel{}, "orders").SetKeys(true, "ID")
 	if !features.Get().StoreARIReplacesInOrders {
diff --git a/sa/model.go b/sa/model.go
index 26313a748..73003cd18 100644
--- a/sa/model.go
+++ b/sa/model.go
@@ -212,15 +212,15 @@ const certStatusFields = "id, serial, status, ocspLastUpdated, revokedDate, revo
 
 // SelectCertificateStatus selects all fields of one certificate status model
 // identified by serial
-func SelectCertificateStatus(ctx context.Context, s db.OneSelector, serial string) (core.CertificateStatus, error) {
-	var model core.CertificateStatus
+func SelectCertificateStatus(ctx context.Context, s db.OneSelector, serial string) (*corepb.CertificateStatus, error) {
+	var model certificateStatusModel
 	err := s.SelectOne(
 		ctx,
 		&model,
 		"SELECT "+certStatusFields+" FROM certificateStatus WHERE serial = ? LIMIT 1",
 		serial,
 	)
-	return model, err
+	return model.toPb(), err
 }
 
 // RevocationStatusModel represents a small subset of the columns in the
@@ -393,6 +393,33 @@ func (model certificateModel) toPb() *corepb.Certificate {
 	}
 }
 
+type certificateStatusModel struct {
+	ID                    int64             `db:"id"`
+	Serial                string            `db:"serial"`
+	Status                core.OCSPStatus   `db:"status"`
+	OCSPLastUpdated       time.Time         `db:"ocspLastUpdated"`
+	RevokedDate           time.Time         `db:"revokedDate"`
+	RevokedReason         revocation.Reason `db:"revokedReason"`
+	LastExpirationNagSent time.Time         `db:"lastExpirationNagSent"`
+	NotAfter              time.Time         `db:"notAfter"`
+	IsExpired             bool              `db:"isExpired"`
+	IssuerID              int64             `db:"issuerID"`
+}
+
+func (model certificateStatusModel) toPb() *corepb.CertificateStatus {
+	return &corepb.CertificateStatus{
+		Serial:                model.Serial,
+		Status:                string(model.Status),
+		OcspLastUpdated:       timestamppb.New(model.OCSPLastUpdated),
+		RevokedDate:           timestamppb.New(model.RevokedDate),
+		RevokedReason:         int64(model.RevokedReason),
+		LastExpirationNagSent: timestamppb.New(model.LastExpirationNagSent),
+		NotAfter:              timestamppb.New(model.NotAfter),
+		IsExpired:             model.IsExpired,
+		IssuerID:              model.IssuerID,
+	}
+}
+
 // orderModel represents one row in the orders table. The CertificateProfileName
 // column is a pointer because the column is NULL-able.
 type orderModel struct {
diff --git a/sa/sa.go b/sa/sa.go
index a7b9cb933..9ee4b5521 100644
--- a/sa/sa.go
+++ b/sa/sa.go
@@ -330,7 +330,7 @@ func (ssa *SQLStorageAuthority) AddPrecertificate(ctx context.Context, req *sapb
 		if req.OcspNotReady {
 			status = core.OCSPStatusNotReady
 		}
-		cs := &core.CertificateStatus{
+		cs := &certificateStatusModel{
 			Serial:                serialHex,
 			Status:                status,
 			OCSPLastUpdated:       ssa.clk.Now(),
@@ -339,7 +339,7 @@ func (ssa *SQLStorageAuthority) AddPrecertificate(ctx context.Context, req *sapb
 			LastExpirationNagSent: time.Time{},
 			NotAfter:              parsed.NotAfter,
 			IsExpired:             false,
-			IssuerNameID:          req.IssuerNameID,
+			IssuerID:              req.IssuerNameID,
 		}
 		err = ssa.dbMap.Insert(ctx, cs)
 		if err != nil {
@@ -1374,9 +1374,9 @@ func (ssa *SQLStorageAuthority) PauseIdentifiers(ctx context.Context, req *sapb.
 			err := tx.SelectOne(ctx, &entry, `
 			SELECT pausedAt, unpausedAt
 			FROM paused
-			WHERE 
-				registrationID = ? AND 
-				identifierType = ? AND 
+			WHERE
+				registrationID = ? AND
+				identifierType = ? AND
 				identifierValue = ?`,
 				req.RegistrationID,
 				ident.Type,
@@ -1420,9 +1420,9 @@ func (ssa *SQLStorageAuthority) PauseIdentifiers(ctx context.Context, req *sapb.
 				UPDATE paused
 				SET pausedAt = ?,
 				    unpausedAt = NULL
-				WHERE 
-					registrationID = ? AND 
-					identifierType = ? AND 
+				WHERE
+					registrationID = ? AND
+					identifierType = ? AND
 					identifierValue = ? AND
 					unpausedAt IS NOT NULL`,
 					ssa.clk.Now().Truncate(time.Second),
@@ -1468,7 +1468,7 @@ func (ssa *SQLStorageAuthority) UnpauseAccount(ctx context.Context, req *sapb.Re
 		result, err := ssa.dbMap.ExecContext(ctx, `
 			UPDATE paused
 			SET unpausedAt = ?
-			WHERE 
+			WHERE
 				registrationID = ? AND
 				unpausedAt IS NULL
 			LIMIT ?`,
diff --git a/sa/saro.go b/sa/saro.go
index fe30f474e..429642f5c 100644
--- a/sa/saro.go
+++ b/sa/saro.go
@@ -20,7 +20,6 @@ import (
 	corepb "github.com/letsencrypt/boulder/core/proto"
 	"github.com/letsencrypt/boulder/db"
 	berrors "github.com/letsencrypt/boulder/errors"
-	bgrpc "github.com/letsencrypt/boulder/grpc"
 	"github.com/letsencrypt/boulder/identifier"
 	blog "github.com/letsencrypt/boulder/log"
 	sapb "github.com/letsencrypt/boulder/sa/proto"
@@ -258,7 +257,7 @@ func (ssa *SQLStorageAuthorityRO) GetCertificateStatus(ctx context.Context, req
 		return nil, err
 	}
 
-	return bgrpc.CertStatusToPB(certStatus), nil
+	return certStatus, nil
 }
 
 // GetRevocationStatus takes a hexadecimal string representing the full serial
diff --git a/va/va.go b/va/va.go
index 57697dee6..5e7732d69 100644
--- a/va/va.go
+++ b/va/va.go
@@ -25,7 +25,6 @@ import (
 	"github.com/letsencrypt/boulder/core"
 	corepb "github.com/letsencrypt/boulder/core/proto"
 	berrors "github.com/letsencrypt/boulder/errors"
-	"github.com/letsencrypt/boulder/features"
 	bgrpc "github.com/letsencrypt/boulder/grpc"
 	"github.com/letsencrypt/boulder/identifier"
 	blog "github.com/letsencrypt/boulder/log"
@@ -610,18 +609,6 @@ func (va *ValidationAuthorityImpl) doRemoteOperation(ctx context.Context, op rem
 			firstProb = currProb
 		}
 
-		if !features.Get().MPICFullResults {
-			// To respond faster, if we get enough successes or too many failures, we cancel remaining RPCs.
-			// Finish the loop to collect remaining responses into `failed` so we can rely on having a response
-			// for every request we made.
-			if len(passed) >= required && len(passedRIRs) >= requiredRIRs {
-				cancel()
-			}
-			if len(failed) > va.maxRemoteFailures {
-				cancel()
-			}
-		}
-
 		// Once all the VAs have returned a result, break the loop.
 		if len(passed)+len(failed) >= remoteVACount {
 			break
diff --git a/vendor/github.com/google/certificate-transparency-go/jsonclient/client.go b/vendor/github.com/google/certificate-transparency-go/jsonclient/client.go
index 7fe967aa6..edb8f919a 100644
--- a/vendor/github.com/google/certificate-transparency-go/jsonclient/client.go
+++ b/vendor/github.com/google/certificate-transparency-go/jsonclient/client.go
@@ -34,7 +34,6 @@ import (
 
 	ct "github.com/google/certificate-transparency-go"
 	"github.com/google/certificate-transparency-go/x509"
-	"k8s.io/klog/v2"
 )
 
 const maxJitter = 250 * time.Millisecond
@@ -182,7 +181,6 @@ func (c *JSONClient) GetAndParse(ctx context.Context, path string, params map[st
 		vals.Add(k, v)
 	}
 	fullURI := fmt.Sprintf("%s%s?%s", c.uri, path, vals.Encode())
-	klog.V(2).Infof("GET %s", fullURI)
 	httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, fullURI, nil)
 	if err != nil {
 		return nil, nil, err
@@ -231,7 +229,6 @@ func (c *JSONClient) PostAndParse(ctx context.Context, path string, req, rsp int
 		return nil, nil, err
 	}
 	fullURI := fmt.Sprintf("%s%s", c.uri, path)
-	klog.V(2).Infof("POST %s", fullURI)
 	httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, fullURI, bytes.NewReader(postBody))
 	if err != nil {
 		return nil, nil, err
diff --git a/vendor/github.com/google/certificate-transparency-go/loglist3/logfilter.go b/vendor/github.com/google/certificate-transparency-go/loglist3/logfilter.go
index 34949be05..9ac54bae9 100644
--- a/vendor/github.com/google/certificate-transparency-go/loglist3/logfilter.go
+++ b/vendor/github.com/google/certificate-transparency-go/loglist3/logfilter.go
@@ -17,7 +17,6 @@ package loglist3
 import (
 	"github.com/google/certificate-transparency-go/x509"
 	"github.com/google/certificate-transparency-go/x509util"
-	"k8s.io/klog/v2"
 )
 
 // LogRoots maps Log-URLs (stated at LogList) to the pools of their accepted
@@ -68,7 +67,8 @@ func (ll *LogList) RootCompatible(certRoot *x509.Certificate, roots LogRoots) Lo
 
 	// Check whether root is a CA-cert.
 	if certRoot != nil && !certRoot.IsCA {
-		klog.Warningf("Compatible method expects fully rooted chain, while last cert of the chain provided is not root")
+		// Compatible method expects fully rooted chain, while last cert of the chain provided is not root.
+		// Proceed anyway.
 		return compatible
 	}
 
diff --git a/vendor/github.com/google/certificate-transparency-go/x509util/pem_cert_pool.go b/vendor/github.com/google/certificate-transparency-go/x509util/pem_cert_pool.go
index e419659fa..c21bd6505 100644
--- a/vendor/github.com/google/certificate-transparency-go/x509util/pem_cert_pool.go
+++ b/vendor/github.com/google/certificate-transparency-go/x509util/pem_cert_pool.go
@@ -22,7 +22,6 @@ import (
 	"os"
 
 	"github.com/google/certificate-transparency-go/x509"
-	"k8s.io/klog/v2"
 )
 
 // String for certificate blocks in BEGIN / END PEM headers
@@ -80,7 +79,6 @@ func (p *PEMCertPool) AppendCertsFromPEM(pemCerts []byte) (ok bool) {
 
 		cert, err := x509.ParseCertificate(block.Bytes)
 		if x509.IsFatal(err) {
-			klog.Warningf("error parsing PEM certificate: %v", err)
 			return false
 		}
 
diff --git a/vendor/k8s.io/klog/v2/.gitignore b/vendor/k8s.io/klog/v2/.gitignore
deleted file mode 100644
index 0aa200239..000000000
--- a/vendor/k8s.io/klog/v2/.gitignore
+++ /dev/null
@@ -1,17 +0,0 @@
-# OSX leaves these everywhere on SMB shares
-._*
-
-# OSX trash
-.DS_Store
-
-# Eclipse files
-.classpath
-.project
-.settings/**
-
-# Files generated by JetBrains IDEs, e.g. IntelliJ IDEA
-.idea/
-*.iml
-
-# Vscode files
-.vscode
diff --git a/vendor/k8s.io/klog/v2/.golangci.yaml b/vendor/k8s.io/klog/v2/.golangci.yaml
deleted file mode 100644
index 0d77d65f0..000000000
--- a/vendor/k8s.io/klog/v2/.golangci.yaml
+++ /dev/null
@@ -1,6 +0,0 @@
-linters:
-  disable-all: true
-  enable: # sorted alphabetical
-    - gofmt
-    - misspell
-    - revive
diff --git a/vendor/k8s.io/klog/v2/CONTRIBUTING.md b/vendor/k8s.io/klog/v2/CONTRIBUTING.md
deleted file mode 100644
index 2641b1f41..000000000
--- a/vendor/k8s.io/klog/v2/CONTRIBUTING.md
+++ /dev/null
@@ -1,22 +0,0 @@
-# Contributing Guidelines
-
-Welcome to Kubernetes. We are excited about the prospect of you joining our [community](https://github.com/kubernetes/community)! The Kubernetes community abides by the CNCF [code of conduct](code-of-conduct.md). Here is an excerpt:
-
-_As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities._
-
-## Getting Started
-
-We have full documentation on how to get started contributing here:
-
-- [Contributor License Agreement](https://git.k8s.io/community/CLA.md) Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests
-- [Kubernetes Contributor Guide](http://git.k8s.io/community/contributors/guide) - Main contributor documentation, or you can just jump directly to the [contributing section](http://git.k8s.io/community/contributors/guide#contributing)
-- [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet) - Common resources for existing developers
-
-## Mentorship
-
-- [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - We have a diverse set of mentorship programs available that are always looking for volunteers!
-
-## Contact Information
-
-- [Slack](https://kubernetes.slack.com/messages/sig-architecture)
-- [Mailing List](https://groups.google.com/forum/#!forum/kubernetes-sig-architecture)
diff --git a/vendor/k8s.io/klog/v2/LICENSE b/vendor/k8s.io/klog/v2/LICENSE
deleted file mode 100644
index 37ec93a14..000000000
--- a/vendor/k8s.io/klog/v2/LICENSE
+++ /dev/null
@@ -1,191 +0,0 @@
-Apache License
-Version 2.0, January 2004
-http://www.apache.org/licenses/
-
-TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-1. Definitions.
-
-"License" shall mean the terms and conditions for use, reproduction, and
-distribution as defined by Sections 1 through 9 of this document.
-
-"Licensor" shall mean the copyright owner or entity authorized by the copyright
-owner that is granting the License.
-
-"Legal Entity" shall mean the union of the acting entity and all other entities
-that control, are controlled by, or are under common control with that entity.
-For the purposes of this definition, "control" means (i) the power, direct or
-indirect, to cause the direction or management of such entity, whether by
-contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
-outstanding shares, or (iii) beneficial ownership of such entity.
-
-"You" (or "Your") shall mean an individual or Legal Entity exercising
-permissions granted by this License.
-
-"Source" form shall mean the preferred form for making modifications, including
-but not limited to software source code, documentation source, and configuration
-files.
-
-"Object" form shall mean any form resulting from mechanical transformation or
-translation of a Source form, including but not limited to compiled object code,
-generated documentation, and conversions to other media types.
-
-"Work" shall mean the work of authorship, whether in Source or Object form, made
-available under the License, as indicated by a copyright notice that is included
-in or attached to the work (an example is provided in the Appendix below).
-
-"Derivative Works" shall mean any work, whether in Source or Object form, that
-is based on (or derived from) the Work and for which the editorial revisions,
-annotations, elaborations, or other modifications represent, as a whole, an
-original work of authorship. For the purposes of this License, Derivative Works
-shall not include works that remain separable from, or merely link (or bind by
-name) to the interfaces of, the Work and Derivative Works thereof.
-
-"Contribution" shall mean any work of authorship, including the original version
-of the Work and any modifications or additions to that Work or Derivative Works
-thereof, that is intentionally submitted to Licensor for inclusion in the Work
-by the copyright owner or by an individual or Legal Entity authorized to submit
-on behalf of the copyright owner. For the purposes of this definition,
-"submitted" means any form of electronic, verbal, or written communication sent
-to the Licensor or its representatives, including but not limited to
-communication on electronic mailing lists, source code control systems, and
-issue tracking systems that are managed by, or on behalf of, the Licensor for
-the purpose of discussing and improving the Work, but excluding communication
-that is conspicuously marked or otherwise designated in writing by the copyright
-owner as "Not a Contribution."
-
-"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
-of whom a Contribution has been received by Licensor and subsequently
-incorporated within the Work.
-
-2. Grant of Copyright License.
-
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable copyright license to reproduce, prepare Derivative Works of,
-publicly display, publicly perform, sublicense, and distribute the Work and such
-Derivative Works in Source or Object form.
-
-3. Grant of Patent License.
-
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable (except as stated in this section) patent license to make, have
-made, use, offer to sell, sell, import, and otherwise transfer the Work, where
-such license applies only to those patent claims licensable by such Contributor
-that are necessarily infringed by their Contribution(s) alone or by combination
-of their Contribution(s) with the Work to which such Contribution(s) was
-submitted. If You institute patent litigation against any entity (including a
-cross-claim or counterclaim in a lawsuit) alleging that the Work or a
-Contribution incorporated within the Work constitutes direct or contributory
-patent infringement, then any patent licenses granted to You under this License
-for that Work shall terminate as of the date such litigation is filed.
-
-4. Redistribution.
-
-You may reproduce and distribute copies of the Work or Derivative Works thereof
-in any medium, with or without modifications, and in Source or Object form,
-provided that You meet the following conditions:
-
-You must give any other recipients of the Work or Derivative Works a copy of
-this License; and
-You must cause any modified files to carry prominent notices stating that You
-changed the files; and
-You must retain, in the Source form of any Derivative Works that You distribute,
-all copyright, patent, trademark, and attribution notices from the Source form
-of the Work, excluding those notices that do not pertain to any part of the
-Derivative Works; and
-If the Work includes a "NOTICE" text file as part of its distribution, then any
-Derivative Works that You distribute must include a readable copy of the
-attribution notices contained within such NOTICE file, excluding those notices
-that do not pertain to any part of the Derivative Works, in at least one of the
-following places: within a NOTICE text file distributed as part of the
-Derivative Works; within the Source form or documentation, if provided along
-with the Derivative Works; or, within a display generated by the Derivative
-Works, if and wherever such third-party notices normally appear. The contents of
-the NOTICE file are for informational purposes only and do not modify the
-License. You may add Your own attribution notices within Derivative Works that
-You distribute, alongside or as an addendum to the NOTICE text from the Work,
-provided that such additional attribution notices cannot be construed as
-modifying the License.
-You may add Your own copyright statement to Your modifications and may provide
-additional or different license terms and conditions for use, reproduction, or
-distribution of Your modifications, or for any such Derivative Works as a whole,
-provided Your use, reproduction, and distribution of the Work otherwise complies
-with the conditions stated in this License.
-
-5. Submission of Contributions.
-
-Unless You explicitly state otherwise, any Contribution intentionally submitted
-for inclusion in the Work by You to the Licensor shall be under the terms and
-conditions of this License, without any additional terms or conditions.
-Notwithstanding the above, nothing herein shall supersede or modify the terms of
-any separate license agreement you may have executed with Licensor regarding
-such Contributions.
-
-6. Trademarks.
-
-This License does not grant permission to use the trade names, trademarks,
-service marks, or product names of the Licensor, except as required for
-reasonable and customary use in describing the origin of the Work and
-reproducing the content of the NOTICE file.
-
-7. Disclaimer of Warranty.
-
-Unless required by applicable law or agreed to in writing, Licensor provides the
-Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
-including, without limitation, any warranties or conditions of TITLE,
-NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
-solely responsible for determining the appropriateness of using or
-redistributing the Work and assume any risks associated with Your exercise of
-permissions under this License.
-
-8. Limitation of Liability.
-
-In no event and under no legal theory, whether in tort (including negligence),
-contract, or otherwise, unless required by applicable law (such as deliberate
-and grossly negligent acts) or agreed to in writing, shall any Contributor be
-liable to You for damages, including any direct, indirect, special, incidental,
-or consequential damages of any character arising as a result of this License or
-out of the use or inability to use the Work (including but not limited to
-damages for loss of goodwill, work stoppage, computer failure or malfunction, or
-any and all other commercial damages or losses), even if such Contributor has
-been advised of the possibility of such damages.
-
-9. Accepting Warranty or Additional Liability.
-
-While redistributing the Work or Derivative Works thereof, You may choose to
-offer, and charge a fee for, acceptance of support, warranty, indemnity, or
-other liability obligations and/or rights consistent with this License. However,
-in accepting such obligations, You may act only on Your own behalf and on Your
-sole responsibility, not on behalf of any other Contributor, and only if You
-agree to indemnify, defend, and hold each Contributor harmless for any liability
-incurred by, or claims asserted against, such Contributor by reason of your
-accepting any such warranty or additional liability.
-
-END OF TERMS AND CONDITIONS
-
-APPENDIX: How to apply the Apache License to your work
-
-To apply the Apache License to your work, attach the following boilerplate
-notice, with the fields enclosed by brackets "[]" replaced with your own
-identifying information. (Don't include the brackets!) The text should be
-enclosed in the appropriate comment syntax for the file format. We also
-recommend that a file or class name and description of purpose be included on
-the same "printed page" as the copyright notice for easier identification within
-third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-     http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
diff --git a/vendor/k8s.io/klog/v2/OWNERS b/vendor/k8s.io/klog/v2/OWNERS
deleted file mode 100644
index 7500475a6..000000000
--- a/vendor/k8s.io/klog/v2/OWNERS
+++ /dev/null
@@ -1,16 +0,0 @@
-# See the OWNERS docs at https://go.k8s.io/owners
-reviewers:
-  - harshanarayana
-  - mengjiao-liu
-  - pohly
-approvers:
-  - dims
-  - pohly
-  - thockin
-emeritus_approvers:
-  - brancz
-  - justinsb
-  - lavalamp
-  - piosz
-  - serathius
-  - tallclair
diff --git a/vendor/k8s.io/klog/v2/README.md b/vendor/k8s.io/klog/v2/README.md
deleted file mode 100644
index d45cbe172..000000000
--- a/vendor/k8s.io/klog/v2/README.md
+++ /dev/null
@@ -1,118 +0,0 @@
-klog
-====
-
-klog is a permanent fork of https://github.com/golang/glog.
-
-## Why was klog created?
-
-The decision to create klog was one that wasn't made lightly, but it was necessary due to some
-drawbacks that are present in [glog](https://github.com/golang/glog). Ultimately, the fork was created due to glog not being under active development; this can be seen in the glog README:
-
-> The code in this repo [...] is not itself under development
-
-This makes us unable to solve many use cases without a fork. The factors that contributed to needing feature development are listed below:
-
- * `glog` [presents a lot "gotchas"](https://github.com/kubernetes/kubernetes/issues/61006) and introduces challenges in containerized environments, all of which aren't well documented.
- * `glog` doesn't provide an easy way to test logs, which detracts from the stability of software using it
- * A long term goal is to implement a logging interface that allows us to add context, change output format, etc.
- 
-Historical context is available here:
-
- * https://github.com/kubernetes/kubernetes/issues/61006
- * https://github.com/kubernetes/kubernetes/issues/70264
- * https://groups.google.com/forum/#!msg/kubernetes-sig-architecture/wCWiWf3Juzs/hXRVBH90CgAJ
- * https://groups.google.com/forum/#!msg/kubernetes-dev/7vnijOMhLS0/1oRiNtigBgAJ
-
-## Release versioning
-
-Semantic versioning is used in this repository. It contains several Go modules
-with different levels of stability:
-- `k8s.io/klog/v2` - stable API, `vX.Y.Z` tags
-- `examples` - no stable API, no tags, no intention to ever stabilize
-
-Exempt from the API stability guarantee are items (packages, functions, etc.)
-which are marked explicitly as `EXPERIMENTAL` in their docs comment. Those
-may still change in incompatible ways or get removed entirely. This can only
-be used for code that is used in tests to avoid situations where non-test
-code from two different Kubernetes dependencies depends on incompatible
-releases of klog because an experimental API was changed.
-
-----
-
-How to use klog
-===============
-- Replace imports for `"github.com/golang/glog"` with `"k8s.io/klog/v2"`
-- Use `klog.InitFlags(nil)` explicitly for initializing global flags as we no longer use `init()` method to register the flags
-- You can now use `log_file` instead of `log_dir` for logging to a single file (See `examples/log_file/usage_log_file.go`)
-- If you want to redirect everything logged using klog somewhere else (say syslog!), you can use `klog.SetOutput()` method and supply a `io.Writer`. (See `examples/set_output/usage_set_output.go`)
-- For more logging conventions (See [Logging Conventions](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md))
-- See our documentation on [pkg.go.dev/k8s.io](https://pkg.go.dev/k8s.io/klog).
-
-**NOTE**: please use the newer go versions that support semantic import versioning in modules, ideally go 1.11.4 or greater.
-
-### Coexisting with klog/v2
-
-See [this example](examples/coexist_klog_v1_and_v2/) to see how to coexist with both klog/v1 and klog/v2.
-
-### Coexisting with glog
-This package can be used side by side with glog. [This example](examples/coexist_glog/coexist_glog.go) shows how to initialize and synchronize flags from the global `flag.CommandLine` FlagSet. In addition, the example makes use of stderr as combined output by setting `alsologtostderr` (or `logtostderr`) to `true`.
-
-## Community, discussion, contribution, and support
-
-Learn how to engage with the Kubernetes community on the [community page](http://kubernetes.io/community/).
-
-You can reach the maintainers of this project at:
-
-- [Slack](https://kubernetes.slack.com/messages/klog)
-- [Mailing List](https://groups.google.com/forum/#!forum/kubernetes-sig-architecture)
-
-### Code of conduct
-
-Participation in the Kubernetes community is governed by the [Kubernetes Code of Conduct](code-of-conduct.md).
-
-----
-
-glog
-====
-
-Leveled execution logs for Go.
-
-This is an efficient pure Go implementation of leveled logs in the
-manner of the open source C++ package
-	https://github.com/google/glog
-
-By binding methods to booleans it is possible to use the log package
-without paying the expense of evaluating the arguments to the log.
-Through the -vmodule flag, the package also provides fine-grained
-control over logging at the file level.
-
-The comment from glog.go introduces the ideas:
-
-	Package glog implements logging analogous to the Google-internal
-	C++ INFO/ERROR/V setup.  It provides functions Info, Warning,
-	Error, Fatal, plus formatting variants such as Infof. It
-	also provides V-style logging controlled by the -v and
-	-vmodule=file=2 flags.
-
-	Basic examples:
-
-		glog.Info("Prepare to repel boarders")
-
-		glog.Fatalf("Initialization failed: %s", err)
-
-	See the documentation of the V function for an explanation
-	of these examples:
-
-		if glog.V(2) {
-			glog.Info("Starting transaction...")
-		}
-
-		glog.V(2).Infoln("Processed", nItems, "elements")
-
-
-The repository contains an open source version of the log package
-used inside Google. The master copy of the source lives inside
-Google, not here. The code in this repo is for export only and is not itself
-under development. Feature requests will be ignored.
-
-Send bug reports to golang-nuts@googlegroups.com.
diff --git a/vendor/k8s.io/klog/v2/RELEASE.md b/vendor/k8s.io/klog/v2/RELEASE.md
deleted file mode 100644
index b53eb960c..000000000
--- a/vendor/k8s.io/klog/v2/RELEASE.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# Release Process
-
-The `klog` is released on an as-needed basis. The process is as follows:
-
-1. An issue is proposing a new release with a changelog since the last release
-1. All [OWNERS](OWNERS) must LGTM this release
-1. An OWNER runs `git tag -s $VERSION` and inserts the changelog and pushes the tag with `git push $VERSION`
-1. The release issue is closed
-1. An announcement email is sent to `kubernetes-dev@googlegroups.com` with the subject `[ANNOUNCE] kubernetes-template-project $VERSION is released`
diff --git a/vendor/k8s.io/klog/v2/SECURITY.md b/vendor/k8s.io/klog/v2/SECURITY.md
deleted file mode 100644
index 2083d44cd..000000000
--- a/vendor/k8s.io/klog/v2/SECURITY.md
+++ /dev/null
@@ -1,22 +0,0 @@
-# Security Policy
-
-## Security Announcements
-
-Join the [kubernetes-security-announce] group for security and vulnerability announcements.
-
-You can also subscribe to an RSS feed of the above using [this link][kubernetes-security-announce-rss].
-
-## Reporting a Vulnerability
-
-Instructions for reporting a vulnerability can be found on the
-[Kubernetes Security and Disclosure Information] page.
-
-## Supported Versions
-
-Information about supported Kubernetes versions can be found on the
-[Kubernetes version and version skew support policy] page on the Kubernetes website.
-
-[kubernetes-security-announce]: https://groups.google.com/forum/#!forum/kubernetes-security-announce
-[kubernetes-security-announce-rss]: https://groups.google.com/forum/feed/kubernetes-security-announce/msgs/rss_v2_0.xml?num=50
-[Kubernetes version and version skew support policy]: https://kubernetes.io/docs/setup/release/version-skew-policy/#supported-versions
-[Kubernetes Security and Disclosure Information]: https://kubernetes.io/docs/reference/issues-security/security/#report-a-vulnerability
diff --git a/vendor/k8s.io/klog/v2/SECURITY_CONTACTS b/vendor/k8s.io/klog/v2/SECURITY_CONTACTS
deleted file mode 100644
index 6128a5869..000000000
--- a/vendor/k8s.io/klog/v2/SECURITY_CONTACTS
+++ /dev/null
@@ -1,20 +0,0 @@
-# Defined below are the security contacts for this repo.
-#
-# They are the contact point for the Product Security Committee to reach out
-# to for triaging and handling of incoming issues.
-#
-# The below names agree to abide by the
-# [Embargo Policy](https://git.k8s.io/security/private-distributors-list.md#embargo-policy)
-# and will be removed and replaced if they violate that agreement.
-#
-# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE
-# INSTRUCTIONS AT https://kubernetes.io/security/
-
-dims
-thockin
-justinsb
-tallclair
-piosz
-brancz
-DirectXMan12
-lavalamp
diff --git a/vendor/k8s.io/klog/v2/code-of-conduct.md b/vendor/k8s.io/klog/v2/code-of-conduct.md
deleted file mode 100644
index 0d15c00cf..000000000
--- a/vendor/k8s.io/klog/v2/code-of-conduct.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Kubernetes Community Code of Conduct
-
-Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md)
diff --git a/vendor/k8s.io/klog/v2/contextual.go b/vendor/k8s.io/klog/v2/contextual.go
deleted file mode 100644
index 005513f2a..000000000
--- a/vendor/k8s.io/klog/v2/contextual.go
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
-Copyright 2021 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package klog
-
-import (
-	"context"
-
-	"github.com/go-logr/logr"
-)
-
-// This file provides the implementation of
-// https://github.com/kubernetes/enhancements/tree/master/keps/sig-instrumentation/1602-structured-logging
-//
-// SetLogger and ClearLogger were originally added to klog.go and got moved
-// here. Contextual logging adds a way to retrieve a Logger for direct logging
-// without the logging calls in klog.go.
-//
-// The global variables are expected to be modified only during sequential
-// parts of a program (init, serial tests) and therefore are not protected by
-// mutex locking.
-
-var (
-	// klogLogger is used as fallback for logging through the normal klog code
-	// when no Logger is set.
-	klogLogger logr.Logger = logr.New(&klogger{})
-)
-
-// SetLogger sets a Logger implementation that will be used as backing
-// implementation of the traditional klog log calls. klog will do its own
-// verbosity checks before calling logger.V().Info. logger.Error is always
-// called, regardless of the klog verbosity settings.
-//
-// If set, all log lines will be suppressed from the regular output, and
-// redirected to the logr implementation.
-// Use as:
-//
-//	...
-//	klog.SetLogger(zapr.NewLogger(zapLog))
-//
-// To remove a backing logr implemention, use ClearLogger. Setting an
-// empty logger with SetLogger(logr.Logger{}) does not work.
-//
-// Modifying the logger is not thread-safe and should be done while no other
-// goroutines invoke log calls, usually during program initialization.
-func SetLogger(logger logr.Logger) {
-	SetLoggerWithOptions(logger)
-}
-
-// SetLoggerWithOptions is a more flexible version of SetLogger. Without
-// additional options, it behaves exactly like SetLogger. By passing
-// ContextualLogger(true) as option, it can be used to set a logger that then
-// will also get called directly by applications which retrieve it via
-// FromContext, Background, or TODO.
-//
-// Supporting direct calls is recommended because it avoids the overhead of
-// routing log entries through klogr into klog and then into the actual Logger
-// backend.
-func SetLoggerWithOptions(logger logr.Logger, opts ...LoggerOption) {
-	logging.loggerOptions = loggerOptions{}
-	for _, opt := range opts {
-		opt(&logging.loggerOptions)
-	}
-	logging.logger = &logWriter{
-		Logger:          logger,
-		writeKlogBuffer: logging.loggerOptions.writeKlogBuffer,
-	}
-}
-
-// ContextualLogger determines whether the logger passed to
-// SetLoggerWithOptions may also get called directly. Such a logger cannot rely
-// on verbosity checking in klog.
-func ContextualLogger(enabled bool) LoggerOption {
-	return func(o *loggerOptions) {
-		o.contextualLogger = enabled
-	}
-}
-
-// FlushLogger provides a callback for flushing data buffered by the logger.
-func FlushLogger(flush func()) LoggerOption {
-	return func(o *loggerOptions) {
-		o.flush = flush
-	}
-}
-
-// WriteKlogBuffer sets a callback that will be invoked by klog to write output
-// produced by non-structured log calls like Infof.
-//
-// The buffer will contain exactly the same data that klog normally would write
-// into its own output stream(s). In particular this includes the header, if
-// klog is configured to write one. The callback then can divert that data into
-// its own output streams. The buffer may or may not end in a line break.
-//
-// Without such a callback, klog will call the logger's Info or Error method
-// with just the message string (i.e. no header).
-func WriteKlogBuffer(write func([]byte)) LoggerOption {
-	return func(o *loggerOptions) {
-		o.writeKlogBuffer = write
-	}
-}
-
-// LoggerOption implements the functional parameter paradigm for
-// SetLoggerWithOptions.
-type LoggerOption func(o *loggerOptions)
-
-type loggerOptions struct {
-	contextualLogger bool
-	flush            func()
-	writeKlogBuffer  func([]byte)
-}
-
-// logWriter combines a logger (always set) with a write callback (optional).
-type logWriter struct {
-	Logger
-	writeKlogBuffer func([]byte)
-}
-
-// ClearLogger removes a backing Logger implementation if one was set earlier
-// with SetLogger.
-//
-// Modifying the logger is not thread-safe and should be done while no other
-// goroutines invoke log calls, usually during program initialization.
-func ClearLogger() {
-	logging.logger = nil
-	logging.loggerOptions = loggerOptions{}
-}
-
-// EnableContextualLogging controls whether contextual logging is enabled.
-// By default it is enabled. When disabled, FromContext avoids looking up
-// the logger in the context and always returns the global logger.
-// LoggerWithValues, LoggerWithName, and NewContext become no-ops
-// and return their input logger respectively context. This may be useful
-// to avoid the additional overhead for contextual logging.
-//
-// This must be called during initialization before goroutines are started.
-func EnableContextualLogging(enabled bool) {
-	logging.contextualLoggingEnabled = enabled
-}
-
-// FromContext retrieves a logger set by the caller or, if not set,
-// falls back to the program's global logger (a Logger instance or klog
-// itself).
-func FromContext(ctx context.Context) Logger {
-	if logging.contextualLoggingEnabled {
-		if logger, err := logr.FromContext(ctx); err == nil {
-			return logger
-		}
-	}
-
-	return Background()
-}
-
-// TODO can be used as a last resort by code that has no means of
-// receiving a logger from its caller. FromContext or an explicit logger
-// parameter should be used instead.
-func TODO() Logger {
-	return Background()
-}
-
-// Background retrieves the fallback logger. It should not be called before
-// that logger was initialized by the program and not by code that should
-// better receive a logger via its parameters. TODO can be used as a temporary
-// solution for such code.
-func Background() Logger {
-	if logging.loggerOptions.contextualLogger {
-		// Is non-nil because logging.loggerOptions.contextualLogger is
-		// only true if a logger was set.
-		return logging.logger.Logger
-	}
-
-	return klogLogger
-}
-
-// LoggerWithValues returns logger.WithValues(...kv) when
-// contextual logging is enabled, otherwise the logger.
-func LoggerWithValues(logger Logger, kv ...interface{}) Logger {
-	if logging.contextualLoggingEnabled {
-		return logger.WithValues(kv...)
-	}
-	return logger
-}
-
-// LoggerWithName returns logger.WithName(name) when contextual logging is
-// enabled, otherwise the logger.
-func LoggerWithName(logger Logger, name string) Logger {
-	if logging.contextualLoggingEnabled {
-		return logger.WithName(name)
-	}
-	return logger
-}
-
-// NewContext returns logr.NewContext(ctx, logger) when
-// contextual logging is enabled, otherwise ctx.
-func NewContext(ctx context.Context, logger Logger) context.Context {
-	if logging.contextualLoggingEnabled {
-		return logr.NewContext(ctx, logger)
-	}
-	return ctx
-}
diff --git a/vendor/k8s.io/klog/v2/contextual_slog.go b/vendor/k8s.io/klog/v2/contextual_slog.go
deleted file mode 100644
index d3b562521..000000000
--- a/vendor/k8s.io/klog/v2/contextual_slog.go
+++ /dev/null
@@ -1,31 +0,0 @@
-//go:build go1.21
-// +build go1.21
-
-/*
-Copyright 2021 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package klog
-
-import (
-	"log/slog"
-
-	"github.com/go-logr/logr"
-)
-
-// SetSlogLogger reconfigures klog to log through the slog logger. The logger must not be nil.
-func SetSlogLogger(logger *slog.Logger) {
-	SetLoggerWithOptions(logr.FromSlogHandler(logger.Handler()), ContextualLogger(true))
-}
diff --git a/vendor/k8s.io/klog/v2/exit.go b/vendor/k8s.io/klog/v2/exit.go
deleted file mode 100644
index 320a14772..000000000
--- a/vendor/k8s.io/klog/v2/exit.go
+++ /dev/null
@@ -1,69 +0,0 @@
-// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/
-//
-// Copyright 2013 Google Inc. All Rights Reserved.
-// Copyright 2022 The Kubernetes Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package klog
-
-import (
-	"fmt"
-	"os"
-	"time"
-)
-
-var (
-
-	// ExitFlushTimeout is the timeout that klog has traditionally used during
-	// calls like Fatal or Exit when flushing log data right before exiting.
-	// Applications that replace those calls and do not have some specific
-	// requirements like "exit immediately" can use this value as parameter
-	// for FlushAndExit.
-	//
-	// Can be set for testing purpose or to change the application's
-	// default.
-	ExitFlushTimeout = 10 * time.Second
-
-	// OsExit is the function called by FlushAndExit to terminate the program.
-	//
-	// Can be set for testing purpose or to change the application's
-	// default behavior. Note that the function should not simply return
-	// because callers of functions like Fatal will not expect that.
-	OsExit = os.Exit
-)
-
-// FlushAndExit flushes log data for a certain amount of time and then calls
-// os.Exit. Combined with some logging call it provides a replacement for
-// traditional calls like Fatal or Exit.
-func FlushAndExit(flushTimeout time.Duration, exitCode int) {
-	timeoutFlush(flushTimeout)
-	OsExit(exitCode)
-}
-
-// timeoutFlush calls Flush and returns when it completes or after timeout
-// elapses, whichever happens first.  This is needed because the hooks invoked
-// by Flush may deadlock when klog.Fatal is called from a hook that holds
-// a lock. Flushing also might take too long.
-func timeoutFlush(timeout time.Duration) {
-	done := make(chan bool, 1)
-	go func() {
-		Flush() // calls logging.lockAndFlushAll()
-		done <- true
-	}()
-	select {
-	case <-done:
-	case <-time.After(timeout):
-		fmt.Fprintln(os.Stderr, "klog: Flush took longer than", timeout)
-	}
-}
diff --git a/vendor/k8s.io/klog/v2/format.go b/vendor/k8s.io/klog/v2/format.go
deleted file mode 100644
index 63995ca6d..000000000
--- a/vendor/k8s.io/klog/v2/format.go
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package klog
-
-import (
-	"encoding/json"
-	"fmt"
-	"strings"
-
-	"github.com/go-logr/logr"
-)
-
-// Format wraps a value of an arbitrary type and implement fmt.Stringer and
-// logr.Marshaler for them. Stringer returns pretty-printed JSON. MarshalLog
-// returns the original value with a type that has no special methods, in
-// particular no MarshalLog or MarshalJSON.
-//
-// Wrapping values like that is useful when the value has a broken
-// implementation of these special functions (for example, a type which
-// inherits String from TypeMeta, but then doesn't re-implement String) or the
-// implementation produces output that is less readable or unstructured (for
-// example, the generated String functions for Kubernetes API types).
-func Format(obj interface{}) interface{} {
-	return formatAny{Object: obj}
-}
-
-type formatAny struct {
-	Object interface{}
-}
-
-func (f formatAny) String() string {
-	var buffer strings.Builder
-	encoder := json.NewEncoder(&buffer)
-	encoder.SetIndent("", "  ")
-	if err := encoder.Encode(&f.Object); err != nil {
-		return fmt.Sprintf("error marshaling %T to JSON: %v", f, err)
-	}
-	return buffer.String()
-}
-
-func (f formatAny) MarshalLog() interface{} {
-	// Returning a pointer to a pointer ensures that zapr doesn't find a
-	// fmt.Stringer or logr.Marshaler when it checks the type of the
-	// value. It then falls back to reflection, which dumps the value being
-	// pointed to (JSON doesn't have pointers).
-	ptr := &f.Object
-	return &ptr
-}
-
-var _ fmt.Stringer = formatAny{}
-var _ logr.Marshaler = formatAny{}
diff --git a/vendor/k8s.io/klog/v2/imports.go b/vendor/k8s.io/klog/v2/imports.go
deleted file mode 100644
index 602c3ed9e..000000000
--- a/vendor/k8s.io/klog/v2/imports.go
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
-Copyright 2021 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package klog
-
-import (
-	"github.com/go-logr/logr"
-)
-
-// The reason for providing these aliases is to allow code to work with logr
-// without directly importing it.
-
-// Logger in this package is exactly the same as logr.Logger.
-type Logger = logr.Logger
-
-// LogSink in this package is exactly the same as logr.LogSink.
-type LogSink = logr.LogSink
-
-// Runtimeinfo in this package is exactly the same as logr.RuntimeInfo.
-type RuntimeInfo = logr.RuntimeInfo
-
-var (
-	// New is an alias for logr.New.
-	New = logr.New
-)
diff --git a/vendor/k8s.io/klog/v2/internal/buffer/buffer.go b/vendor/k8s.io/klog/v2/internal/buffer/buffer.go
deleted file mode 100644
index 46de00fb0..000000000
--- a/vendor/k8s.io/klog/v2/internal/buffer/buffer.go
+++ /dev/null
@@ -1,184 +0,0 @@
-// Copyright 2013 Google Inc. All Rights Reserved.
-// Copyright 2022 The Kubernetes Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package buffer provides a cache for byte.Buffer instances that can be reused
-// to avoid frequent allocation and deallocation. It also has utility code
-// for log header formatting that use these buffers.
-package buffer
-
-import (
-	"bytes"
-	"os"
-	"sync"
-	"time"
-
-	"k8s.io/klog/v2/internal/severity"
-)
-
-var (
-	// Pid is inserted into log headers. Can be overridden for tests.
-	Pid = os.Getpid()
-
-	// Time, if set, will be used instead of the actual current time.
-	Time *time.Time
-)
-
-// Buffer holds a single byte.Buffer for reuse. The zero value is ready for
-// use. It also provides some helper methods for output formatting.
-type Buffer struct {
-	bytes.Buffer
-	Tmp [64]byte // temporary byte array for creating headers.
-}
-
-var buffers = sync.Pool{
-	New: func() interface{} {
-		return new(Buffer)
-	},
-}
-
-// GetBuffer returns a new, ready-to-use buffer.
-func GetBuffer() *Buffer {
-	b := buffers.Get().(*Buffer)
-	b.Reset()
-	return b
-}
-
-// PutBuffer returns a buffer to the free list.
-func PutBuffer(b *Buffer) {
-	if b.Len() >= 256 {
-		// Let big buffers die a natural death, without relying on
-		// sync.Pool behavior. The documentation implies that items may
-		// get deallocated while stored there ("If the Pool holds the
-		// only reference when this [= be removed automatically]
-		// happens, the item might be deallocated."), but
-		// https://github.com/golang/go/issues/23199 leans more towards
-		// having such a size limit.
-		return
-	}
-
-	buffers.Put(b)
-}
-
-// Some custom tiny helper functions to print the log header efficiently.
-
-const digits = "0123456789"
-
-// twoDigits formats a zero-prefixed two-digit integer at buf.Tmp[i].
-func (buf *Buffer) twoDigits(i, d int) {
-	buf.Tmp[i+1] = digits[d%10]
-	d /= 10
-	buf.Tmp[i] = digits[d%10]
-}
-
-// nDigits formats an n-digit integer at buf.Tmp[i],
-// padding with pad on the left.
-// It assumes d >= 0.
-func (buf *Buffer) nDigits(n, i, d int, pad byte) {
-	j := n - 1
-	for ; j >= 0 && d > 0; j-- {
-		buf.Tmp[i+j] = digits[d%10]
-		d /= 10
-	}
-	for ; j >= 0; j-- {
-		buf.Tmp[i+j] = pad
-	}
-}
-
-// someDigits formats a zero-prefixed variable-width integer at buf.Tmp[i].
-func (buf *Buffer) someDigits(i, d int) int {
-	// Print into the top, then copy down. We know there's space for at least
-	// a 10-digit number.
-	j := len(buf.Tmp)
-	for {
-		j--
-		buf.Tmp[j] = digits[d%10]
-		d /= 10
-		if d == 0 {
-			break
-		}
-	}
-	return copy(buf.Tmp[i:], buf.Tmp[j:])
-}
-
-// FormatHeader formats a log header using the provided file name and line number
-// and writes it into the buffer.
-func (buf *Buffer) FormatHeader(s severity.Severity, file string, line int, now time.Time) {
-	if line < 0 {
-		line = 0 // not a real line number, but acceptable to someDigits
-	}
-	if s > severity.FatalLog {
-		s = severity.InfoLog // for safety.
-	}
-
-	// Avoid Fprintf, for speed. The format is so simple that we can do it quickly by hand.
-	// It's worth about 3X. Fprintf is hard.
-	if Time != nil {
-		now = *Time
-	}
-	_, month, day := now.Date()
-	hour, minute, second := now.Clock()
-	// Lmmdd hh:mm:ss.uuuuuu threadid file:line]
-	buf.Tmp[0] = severity.Char[s]
-	buf.twoDigits(1, int(month))
-	buf.twoDigits(3, day)
-	buf.Tmp[5] = ' '
-	buf.twoDigits(6, hour)
-	buf.Tmp[8] = ':'
-	buf.twoDigits(9, minute)
-	buf.Tmp[11] = ':'
-	buf.twoDigits(12, second)
-	buf.Tmp[14] = '.'
-	buf.nDigits(6, 15, now.Nanosecond()/1000, '0')
-	buf.Tmp[21] = ' '
-	buf.nDigits(7, 22, Pid, ' ') // TODO: should be TID
-	buf.Tmp[29] = ' '
-	buf.Write(buf.Tmp[:30])
-	buf.WriteString(file)
-	buf.Tmp[0] = ':'
-	n := buf.someDigits(1, line)
-	buf.Tmp[n+1] = ']'
-	buf.Tmp[n+2] = ' '
-	buf.Write(buf.Tmp[:n+3])
-}
-
-// SprintHeader formats a log header and returns a string. This is a simpler
-// version of FormatHeader for use in ktesting.
-func (buf *Buffer) SprintHeader(s severity.Severity, now time.Time) string {
-	if s > severity.FatalLog {
-		s = severity.InfoLog // for safety.
-	}
-
-	// Avoid Fprintf, for speed. The format is so simple that we can do it quickly by hand.
-	// It's worth about 3X. Fprintf is hard.
-	if Time != nil {
-		now = *Time
-	}
-	_, month, day := now.Date()
-	hour, minute, second := now.Clock()
-	// Lmmdd hh:mm:ss.uuuuuu threadid file:line]
-	buf.Tmp[0] = severity.Char[s]
-	buf.twoDigits(1, int(month))
-	buf.twoDigits(3, day)
-	buf.Tmp[5] = ' '
-	buf.twoDigits(6, hour)
-	buf.Tmp[8] = ':'
-	buf.twoDigits(9, minute)
-	buf.Tmp[11] = ':'
-	buf.twoDigits(12, second)
-	buf.Tmp[14] = '.'
-	buf.nDigits(6, 15, now.Nanosecond()/1000, '0')
-	buf.Tmp[21] = ']'
-	return string(buf.Tmp[:22])
-}
diff --git a/vendor/k8s.io/klog/v2/internal/clock/README.md b/vendor/k8s.io/klog/v2/internal/clock/README.md
deleted file mode 100644
index 03d692c8f..000000000
--- a/vendor/k8s.io/klog/v2/internal/clock/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# Clock
-
-This package provides an interface for time-based operations.  It allows
-mocking time for testing.
-
-This is a copy of k8s.io/utils/clock. We have to copy it to avoid a circular
-dependency (k8s.io/klog -> k8s.io/utils -> k8s.io/klog).
diff --git a/vendor/k8s.io/klog/v2/internal/clock/clock.go b/vendor/k8s.io/klog/v2/internal/clock/clock.go
deleted file mode 100644
index cc11bb480..000000000
--- a/vendor/k8s.io/klog/v2/internal/clock/clock.go
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
-Copyright 2014 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package clock
-
-import "time"
-
-// PassiveClock allows for injecting fake or real clocks into code
-// that needs to read the current time but does not support scheduling
-// activity in the future.
-type PassiveClock interface {
-	Now() time.Time
-	Since(time.Time) time.Duration
-}
-
-// Clock allows for injecting fake or real clocks into code that
-// needs to do arbitrary things based on time.
-type Clock interface {
-	PassiveClock
-	// After returns the channel of a new Timer.
-	// This method does not allow to free/GC the backing timer before it fires. Use
-	// NewTimer instead.
-	After(d time.Duration) <-chan time.Time
-	// NewTimer returns a new Timer.
-	NewTimer(d time.Duration) Timer
-	// Sleep sleeps for the provided duration d.
-	// Consider making the sleep interruptible by using 'select' on a context channel and a timer channel.
-	Sleep(d time.Duration)
-	// NewTicker returns a new Ticker.
-	NewTicker(time.Duration) Ticker
-}
-
-// WithDelayedExecution allows for injecting fake or real clocks into
-// code that needs to make use of AfterFunc functionality.
-type WithDelayedExecution interface {
-	Clock
-	// AfterFunc executes f in its own goroutine after waiting
-	// for d duration and returns a Timer whose channel can be
-	// closed by calling Stop() on the Timer.
-	AfterFunc(d time.Duration, f func()) Timer
-}
-
-// WithTickerAndDelayedExecution allows for injecting fake or real clocks
-// into code that needs Ticker and AfterFunc functionality
-type WithTickerAndDelayedExecution interface {
-	Clock
-	// AfterFunc executes f in its own goroutine after waiting
-	// for d duration and returns a Timer whose channel can be
-	// closed by calling Stop() on the Timer.
-	AfterFunc(d time.Duration, f func()) Timer
-}
-
-// Ticker defines the Ticker interface.
-type Ticker interface {
-	C() <-chan time.Time
-	Stop()
-}
-
-var _ Clock = RealClock{}
-
-// RealClock really calls time.Now()
-type RealClock struct{}
-
-// Now returns the current time.
-func (RealClock) Now() time.Time {
-	return time.Now()
-}
-
-// Since returns time since the specified timestamp.
-func (RealClock) Since(ts time.Time) time.Duration {
-	return time.Since(ts)
-}
-
-// After is the same as time.After(d).
-// This method does not allow to free/GC the backing timer before it fires. Use
-// NewTimer instead.
-func (RealClock) After(d time.Duration) <-chan time.Time {
-	return time.After(d)
-}
-
-// NewTimer is the same as time.NewTimer(d)
-func (RealClock) NewTimer(d time.Duration) Timer {
-	return &realTimer{
-		timer: time.NewTimer(d),
-	}
-}
-
-// AfterFunc is the same as time.AfterFunc(d, f).
-func (RealClock) AfterFunc(d time.Duration, f func()) Timer {
-	return &realTimer{
-		timer: time.AfterFunc(d, f),
-	}
-}
-
-// NewTicker returns a new Ticker.
-func (RealClock) NewTicker(d time.Duration) Ticker {
-	return &realTicker{
-		ticker: time.NewTicker(d),
-	}
-}
-
-// Sleep is the same as time.Sleep(d)
-// Consider making the sleep interruptible by using 'select' on a context channel and a timer channel.
-func (RealClock) Sleep(d time.Duration) {
-	time.Sleep(d)
-}
-
-// Timer allows for injecting fake or real timers into code that
-// needs to do arbitrary things based on time.
-type Timer interface {
-	C() <-chan time.Time
-	Stop() bool
-	Reset(d time.Duration) bool
-}
-
-var _ = Timer(&realTimer{})
-
-// realTimer is backed by an actual time.Timer.
-type realTimer struct {
-	timer *time.Timer
-}
-
-// C returns the underlying timer's channel.
-func (r *realTimer) C() <-chan time.Time {
-	return r.timer.C
-}
-
-// Stop calls Stop() on the underlying timer.
-func (r *realTimer) Stop() bool {
-	return r.timer.Stop()
-}
-
-// Reset calls Reset() on the underlying timer.
-func (r *realTimer) Reset(d time.Duration) bool {
-	return r.timer.Reset(d)
-}
-
-type realTicker struct {
-	ticker *time.Ticker
-}
-
-func (r *realTicker) C() <-chan time.Time {
-	return r.ticker.C
-}
-
-func (r *realTicker) Stop() {
-	r.ticker.Stop()
-}
diff --git a/vendor/k8s.io/klog/v2/internal/dbg/dbg.go b/vendor/k8s.io/klog/v2/internal/dbg/dbg.go
deleted file mode 100644
index f27bd1447..000000000
--- a/vendor/k8s.io/klog/v2/internal/dbg/dbg.go
+++ /dev/null
@@ -1,42 +0,0 @@
-// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/
-//
-// Copyright 2013 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package dbg provides some helper code for call traces.
-package dbg
-
-import (
-	"runtime"
-)
-
-// Stacks is a wrapper for runtime.Stack that attempts to recover the data for
-// all goroutines or the calling one.
-func Stacks(all bool) []byte {
-	// We don't know how big the traces are, so grow a few times if they don't fit. Start large, though.
-	n := 10000
-	if all {
-		n = 100000
-	}
-	var trace []byte
-	for i := 0; i < 5; i++ {
-		trace = make([]byte, n)
-		nbytes := runtime.Stack(trace, all)
-		if nbytes < len(trace) {
-			return trace[:nbytes]
-		}
-		n *= 2
-	}
-	return trace
-}
diff --git a/vendor/k8s.io/klog/v2/internal/serialize/keyvalues.go b/vendor/k8s.io/klog/v2/internal/serialize/keyvalues.go
deleted file mode 100644
index d1a4751c9..000000000
--- a/vendor/k8s.io/klog/v2/internal/serialize/keyvalues.go
+++ /dev/null
@@ -1,292 +0,0 @@
-/*
-Copyright 2021 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package serialize
-
-import (
-	"bytes"
-	"encoding/json"
-	"fmt"
-	"strconv"
-
-	"github.com/go-logr/logr"
-)
-
-type textWriter interface {
-	WriteText(*bytes.Buffer)
-}
-
-// WithValues implements LogSink.WithValues. The old key/value pairs are
-// assumed to be well-formed, the new ones are checked and padded if
-// necessary. It returns a new slice.
-func WithValues(oldKV, newKV []interface{}) []interface{} {
-	if len(newKV) == 0 {
-		return oldKV
-	}
-	newLen := len(oldKV) + len(newKV)
-	hasMissingValue := newLen%2 != 0
-	if hasMissingValue {
-		newLen++
-	}
-	// The new LogSink must have its own slice.
-	kv := make([]interface{}, 0, newLen)
-	kv = append(kv, oldKV...)
-	kv = append(kv, newKV...)
-	if hasMissingValue {
-		kv = append(kv, missingValue)
-	}
-	return kv
-}
-
-// MergeKVs deduplicates elements provided in two key/value slices.
-//
-// Keys in each slice are expected to be unique, so duplicates can only occur
-// when the first and second slice contain the same key. When that happens, the
-// key/value pair from the second slice is used. The first slice must be well-formed
-// (= even key/value pairs). The second one may have a missing value, in which
-// case the special "missing value" is added to the result.
-func MergeKVs(first, second []interface{}) []interface{} {
-	maxLength := len(first) + (len(second)+1)/2*2
-	if maxLength == 0 {
-		// Nothing to do at all.
-		return nil
-	}
-
-	if len(first) == 0 && len(second)%2 == 0 {
-		// Nothing to be overridden, second slice is well-formed
-		// and can be used directly.
-		return second
-	}
-
-	// Determine which keys are in the second slice so that we can skip
-	// them when iterating over the first one. The code intentionally
-	// favors performance over completeness: we assume that keys are string
-	// constants and thus compare equal when the string values are equal. A
-	// string constant being overridden by, for example, a fmt.Stringer is
-	// not handled.
-	overrides := map[interface{}]bool{}
-	for i := 0; i < len(second); i += 2 {
-		overrides[second[i]] = true
-	}
-	merged := make([]interface{}, 0, maxLength)
-	for i := 0; i+1 < len(first); i += 2 {
-		key := first[i]
-		if overrides[key] {
-			continue
-		}
-		merged = append(merged, key, first[i+1])
-	}
-	merged = append(merged, second...)
-	if len(merged)%2 != 0 {
-		merged = append(merged, missingValue)
-	}
-	return merged
-}
-
-type Formatter struct {
-	AnyToStringHook AnyToStringFunc
-}
-
-type AnyToStringFunc func(v interface{}) string
-
-// MergeKVsInto is a variant of MergeKVs which directly formats the key/value
-// pairs into a buffer.
-func (f Formatter) MergeAndFormatKVs(b *bytes.Buffer, first, second []interface{}) {
-	if len(first) == 0 && len(second) == 0 {
-		// Nothing to do at all.
-		return
-	}
-
-	if len(first) == 0 && len(second)%2 == 0 {
-		// Nothing to be overridden, second slice is well-formed
-		// and can be used directly.
-		for i := 0; i < len(second); i += 2 {
-			f.KVFormat(b, second[i], second[i+1])
-		}
-		return
-	}
-
-	// Determine which keys are in the second slice so that we can skip
-	// them when iterating over the first one. The code intentionally
-	// favors performance over completeness: we assume that keys are string
-	// constants and thus compare equal when the string values are equal. A
-	// string constant being overridden by, for example, a fmt.Stringer is
-	// not handled.
-	overrides := map[interface{}]bool{}
-	for i := 0; i < len(second); i += 2 {
-		overrides[second[i]] = true
-	}
-	for i := 0; i < len(first); i += 2 {
-		key := first[i]
-		if overrides[key] {
-			continue
-		}
-		f.KVFormat(b, key, first[i+1])
-	}
-	// Round down.
-	l := len(second)
-	l = l / 2 * 2
-	for i := 1; i < l; i += 2 {
-		f.KVFormat(b, second[i-1], second[i])
-	}
-	if len(second)%2 == 1 {
-		f.KVFormat(b, second[len(second)-1], missingValue)
-	}
-}
-
-func MergeAndFormatKVs(b *bytes.Buffer, first, second []interface{}) {
-	Formatter{}.MergeAndFormatKVs(b, first, second)
-}
-
-const missingValue = "(MISSING)"
-
-// KVListFormat serializes all key/value pairs into the provided buffer.
-// A space gets inserted before the first pair and between each pair.
-func (f Formatter) KVListFormat(b *bytes.Buffer, keysAndValues ...interface{}) {
-	for i := 0; i < len(keysAndValues); i += 2 {
-		var v interface{}
-		k := keysAndValues[i]
-		if i+1 < len(keysAndValues) {
-			v = keysAndValues[i+1]
-		} else {
-			v = missingValue
-		}
-		f.KVFormat(b, k, v)
-	}
-}
-
-func KVListFormat(b *bytes.Buffer, keysAndValues ...interface{}) {
-	Formatter{}.KVListFormat(b, keysAndValues...)
-}
-
-func KVFormat(b *bytes.Buffer, k, v interface{}) {
-	Formatter{}.KVFormat(b, k, v)
-}
-
-// formatAny is the fallback formatter for a value. It supports a hook (for
-// example, for YAML encoding) and itself uses JSON encoding.
-func (f Formatter) formatAny(b *bytes.Buffer, v interface{}) {
-	b.WriteRune('=')
-	if f.AnyToStringHook != nil {
-		b.WriteString(f.AnyToStringHook(v))
-		return
-	}
-	formatAsJSON(b, v)
-}
-
-func formatAsJSON(b *bytes.Buffer, v interface{}) {
-	encoder := json.NewEncoder(b)
-	l := b.Len()
-	if err := encoder.Encode(v); err != nil {
-		// This shouldn't happen. We discard whatever the encoder
-		// wrote and instead dump an error string.
-		b.Truncate(l)
-		b.WriteString(fmt.Sprintf(`"<internal error: %v>"`, err))
-		return
-	}
-	// Remove trailing newline.
-	b.Truncate(b.Len() - 1)
-}
-
-// StringerToString converts a Stringer to a string,
-// handling panics if they occur.
-func StringerToString(s fmt.Stringer) (ret string) {
-	defer func() {
-		if err := recover(); err != nil {
-			ret = fmt.Sprintf("<panic: %s>", err)
-		}
-	}()
-	ret = s.String()
-	return
-}
-
-// MarshalerToValue invokes a marshaler and catches
-// panics.
-func MarshalerToValue(m logr.Marshaler) (ret interface{}) {
-	defer func() {
-		if err := recover(); err != nil {
-			ret = fmt.Sprintf("<panic: %s>", err)
-		}
-	}()
-	ret = m.MarshalLog()
-	return
-}
-
-// ErrorToString converts an error to a string,
-// handling panics if they occur.
-func ErrorToString(err error) (ret string) {
-	defer func() {
-		if err := recover(); err != nil {
-			ret = fmt.Sprintf("<panic: %s>", err)
-		}
-	}()
-	ret = err.Error()
-	return
-}
-
-func writeTextWriterValue(b *bytes.Buffer, v textWriter) {
-	b.WriteByte('=')
-	defer func() {
-		if err := recover(); err != nil {
-			fmt.Fprintf(b, `"<panic: %s>"`, err)
-		}
-	}()
-	v.WriteText(b)
-}
-
-func writeStringValue(b *bytes.Buffer, v string) {
-	data := []byte(v)
-	index := bytes.IndexByte(data, '\n')
-	if index == -1 {
-		b.WriteByte('=')
-		// Simple string, quote quotation marks and non-printable characters.
-		b.WriteString(strconv.Quote(v))
-		return
-	}
-
-	// Complex multi-line string, show as-is with indention like this:
-	// I... "hello world" key=<
-	// <tab>line 1
-	// <tab>line 2
-	//  >
-	//
-	// Tabs indent the lines of the value while the end of string delimiter
-	// is indented with a space. That has two purposes:
-	// - visual difference between the two for a human reader because indention
-	//   will be different
-	// - no ambiguity when some value line starts with the end delimiter
-	//
-	// One downside is that the output cannot distinguish between strings that
-	// end with a line break and those that don't because the end delimiter
-	// will always be on the next line.
-	b.WriteString("=<\n")
-	for index != -1 {
-		b.WriteByte('\t')
-		b.Write(data[0 : index+1])
-		data = data[index+1:]
-		index = bytes.IndexByte(data, '\n')
-	}
-	if len(data) == 0 {
-		// String ended with line break, don't add another.
-		b.WriteString(" >")
-	} else {
-		// No line break at end of last line, write rest of string and
-		// add one.
-		b.WriteByte('\t')
-		b.Write(data)
-		b.WriteString("\n >")
-	}
-}
diff --git a/vendor/k8s.io/klog/v2/internal/serialize/keyvalues_no_slog.go b/vendor/k8s.io/klog/v2/internal/serialize/keyvalues_no_slog.go
deleted file mode 100644
index d9c7d1546..000000000
--- a/vendor/k8s.io/klog/v2/internal/serialize/keyvalues_no_slog.go
+++ /dev/null
@@ -1,97 +0,0 @@
-//go:build !go1.21
-// +build !go1.21
-
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package serialize
-
-import (
-	"bytes"
-	"fmt"
-
-	"github.com/go-logr/logr"
-)
-
-// KVFormat serializes one key/value pair into the provided buffer.
-// A space gets inserted before the pair.
-func (f Formatter) KVFormat(b *bytes.Buffer, k, v interface{}) {
-	// This is the version without slog support. Must be kept in sync with
-	// the version in keyvalues_slog.go.
-
-	b.WriteByte(' ')
-	// Keys are assumed to be well-formed according to
-	// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/migration-to-structured-logging.md#name-arguments
-	// for the sake of performance. Keys with spaces,
-	// special characters, etc. will break parsing.
-	if sK, ok := k.(string); ok {
-		// Avoid one allocation when the key is a string, which
-		// normally it should be.
-		b.WriteString(sK)
-	} else {
-		b.WriteString(fmt.Sprintf("%s", k))
-	}
-
-	// The type checks are sorted so that more frequently used ones
-	// come first because that is then faster in the common
-	// cases. In Kubernetes, ObjectRef (a Stringer) is more common
-	// than plain strings
-	// (https://github.com/kubernetes/kubernetes/pull/106594#issuecomment-975526235).
-	switch v := v.(type) {
-	case textWriter:
-		writeTextWriterValue(b, v)
-	case fmt.Stringer:
-		writeStringValue(b, StringerToString(v))
-	case string:
-		writeStringValue(b, v)
-	case error:
-		writeStringValue(b, ErrorToString(v))
-	case logr.Marshaler:
-		value := MarshalerToValue(v)
-		// A marshaler that returns a string is useful for
-		// delayed formatting of complex values. We treat this
-		// case like a normal string. This is useful for
-		// multi-line support.
-		//
-		// We could do this by recursively formatting a value,
-		// but that comes with the risk of infinite recursion
-		// if a marshaler returns itself. Instead we call it
-		// only once and rely on it returning the intended
-		// value directly.
-		switch value := value.(type) {
-		case string:
-			writeStringValue(b, value)
-		default:
-			f.formatAny(b, value)
-		}
-	case []byte:
-		// In https://github.com/kubernetes/klog/pull/237 it was decided
-		// to format byte slices with "%+q". The advantages of that are:
-		// - readable output if the bytes happen to be printable
-		// - non-printable bytes get represented as unicode escape
-		//   sequences (\uxxxx)
-		//
-		// The downsides are that we cannot use the faster
-		// strconv.Quote here and that multi-line output is not
-		// supported. If developers know that a byte array is
-		// printable and they want multi-line output, they can
-		// convert the value to string before logging it.
-		b.WriteByte('=')
-		b.WriteString(fmt.Sprintf("%+q", v))
-	default:
-		f.formatAny(b, v)
-	}
-}
diff --git a/vendor/k8s.io/klog/v2/internal/serialize/keyvalues_slog.go b/vendor/k8s.io/klog/v2/internal/serialize/keyvalues_slog.go
deleted file mode 100644
index 89acf9772..000000000
--- a/vendor/k8s.io/klog/v2/internal/serialize/keyvalues_slog.go
+++ /dev/null
@@ -1,155 +0,0 @@
-//go:build go1.21
-// +build go1.21
-
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package serialize
-
-import (
-	"bytes"
-	"fmt"
-	"log/slog"
-	"strconv"
-
-	"github.com/go-logr/logr"
-)
-
-// KVFormat serializes one key/value pair into the provided buffer.
-// A space gets inserted before the pair.
-func (f Formatter) KVFormat(b *bytes.Buffer, k, v interface{}) {
-	// This is the version without slog support. Must be kept in sync with
-	// the version in keyvalues_slog.go.
-
-	b.WriteByte(' ')
-	// Keys are assumed to be well-formed according to
-	// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/migration-to-structured-logging.md#name-arguments
-	// for the sake of performance. Keys with spaces,
-	// special characters, etc. will break parsing.
-	if sK, ok := k.(string); ok {
-		// Avoid one allocation when the key is a string, which
-		// normally it should be.
-		b.WriteString(sK)
-	} else {
-		b.WriteString(fmt.Sprintf("%s", k))
-	}
-
-	// The type checks are sorted so that more frequently used ones
-	// come first because that is then faster in the common
-	// cases. In Kubernetes, ObjectRef (a Stringer) is more common
-	// than plain strings
-	// (https://github.com/kubernetes/kubernetes/pull/106594#issuecomment-975526235).
-	//
-	// slog.LogValuer does not need to be handled here because the handler will
-	// already have resolved such special values to the final value for logging.
-	switch v := v.(type) {
-	case textWriter:
-		writeTextWriterValue(b, v)
-	case slog.Value:
-		// This must come before fmt.Stringer because slog.Value implements
-		// fmt.Stringer, but does not produce the output that we want.
-		b.WriteByte('=')
-		generateJSON(b, v)
-	case fmt.Stringer:
-		writeStringValue(b, StringerToString(v))
-	case string:
-		writeStringValue(b, v)
-	case error:
-		writeStringValue(b, ErrorToString(v))
-	case logr.Marshaler:
-		value := MarshalerToValue(v)
-		// A marshaler that returns a string is useful for
-		// delayed formatting of complex values. We treat this
-		// case like a normal string. This is useful for
-		// multi-line support.
-		//
-		// We could do this by recursively formatting a value,
-		// but that comes with the risk of infinite recursion
-		// if a marshaler returns itself. Instead we call it
-		// only once and rely on it returning the intended
-		// value directly.
-		switch value := value.(type) {
-		case string:
-			writeStringValue(b, value)
-		default:
-			f.formatAny(b, value)
-		}
-	case slog.LogValuer:
-		value := slog.AnyValue(v).Resolve()
-		if value.Kind() == slog.KindString {
-			writeStringValue(b, value.String())
-		} else {
-			b.WriteByte('=')
-			generateJSON(b, value)
-		}
-	case []byte:
-		// In https://github.com/kubernetes/klog/pull/237 it was decided
-		// to format byte slices with "%+q". The advantages of that are:
-		// - readable output if the bytes happen to be printable
-		// - non-printable bytes get represented as unicode escape
-		//   sequences (\uxxxx)
-		//
-		// The downsides are that we cannot use the faster
-		// strconv.Quote here and that multi-line output is not
-		// supported. If developers know that a byte array is
-		// printable and they want multi-line output, they can
-		// convert the value to string before logging it.
-		b.WriteByte('=')
-		b.WriteString(fmt.Sprintf("%+q", v))
-	default:
-		f.formatAny(b, v)
-	}
-}
-
-// generateJSON has the same preference for plain strings as KVFormat.
-// In contrast to KVFormat it always produces valid JSON with no line breaks.
-func generateJSON(b *bytes.Buffer, v interface{}) {
-	switch v := v.(type) {
-	case slog.Value:
-		switch v.Kind() {
-		case slog.KindGroup:
-			// Format as a JSON group. We must not involve f.AnyToStringHook (if there is any),
-			// because there is no guarantee that it produces valid JSON.
-			b.WriteByte('{')
-			for i, attr := range v.Group() {
-				if i > 0 {
-					b.WriteByte(',')
-				}
-				b.WriteString(strconv.Quote(attr.Key))
-				b.WriteByte(':')
-				generateJSON(b, attr.Value)
-			}
-			b.WriteByte('}')
-		case slog.KindLogValuer:
-			generateJSON(b, v.Resolve())
-		default:
-			// Peel off the slog.Value wrapper and format the actual value.
-			generateJSON(b, v.Any())
-		}
-	case fmt.Stringer:
-		b.WriteString(strconv.Quote(StringerToString(v)))
-	case logr.Marshaler:
-		generateJSON(b, MarshalerToValue(v))
-	case slog.LogValuer:
-		generateJSON(b, slog.AnyValue(v).Resolve().Any())
-	case string:
-		b.WriteString(strconv.Quote(v))
-	case error:
-		b.WriteString(strconv.Quote(v.Error()))
-	default:
-		formatAsJSON(b, v)
-	}
-}
diff --git a/vendor/k8s.io/klog/v2/internal/severity/severity.go b/vendor/k8s.io/klog/v2/internal/severity/severity.go
deleted file mode 100644
index 30fa1834f..000000000
--- a/vendor/k8s.io/klog/v2/internal/severity/severity.go
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2013 Google Inc. All Rights Reserved.
-// Copyright 2022 The Kubernetes Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package severity provides definitions for klog severity (info, warning, ...)
-package severity
-
-import (
-	"strings"
-)
-
-// severity identifies the sort of log: info, warning etc. The binding to flag.Value
-// is handled in klog.go
-type Severity int32 // sync/atomic int32
-
-// These constants identify the log levels in order of increasing severity.
-// A message written to a high-severity log file is also written to each
-// lower-severity log file.
-const (
-	InfoLog Severity = iota
-	WarningLog
-	ErrorLog
-	FatalLog
-	NumSeverity = 4
-)
-
-// Char contains one shortcut letter per severity level.
-const Char = "IWEF"
-
-// Name contains one name per severity level.
-var Name = []string{
-	InfoLog:    "INFO",
-	WarningLog: "WARNING",
-	ErrorLog:   "ERROR",
-	FatalLog:   "FATAL",
-}
-
-// ByName looks up a severity level by name.
-func ByName(s string) (Severity, bool) {
-	s = strings.ToUpper(s)
-	for i, name := range Name {
-		if name == s {
-			return Severity(i), true
-		}
-	}
-	return 0, false
-}
diff --git a/vendor/k8s.io/klog/v2/internal/sloghandler/sloghandler_slog.go b/vendor/k8s.io/klog/v2/internal/sloghandler/sloghandler_slog.go
deleted file mode 100644
index 21f1697d0..000000000
--- a/vendor/k8s.io/klog/v2/internal/sloghandler/sloghandler_slog.go
+++ /dev/null
@@ -1,96 +0,0 @@
-//go:build go1.21
-// +build go1.21
-
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package sloghandler
-
-import (
-	"context"
-	"log/slog"
-	"runtime"
-	"strings"
-	"time"
-
-	"k8s.io/klog/v2/internal/severity"
-)
-
-func Handle(_ context.Context, record slog.Record, groups string, printWithInfos func(file string, line int, now time.Time, err error, s severity.Severity, msg string, kvList []interface{})) error {
-	now := record.Time
-	if now.IsZero() {
-		// This format doesn't support printing entries without a time.
-		now = time.Now()
-	}
-
-	// slog has numeric severity levels, with 0 as default "info", negative for debugging, and
-	// positive with some pre-defined levels for more important. Those ranges get mapped to
-	// the corresponding klog levels where possible, with "info" the default that is used
-	// also for negative debug levels.
-	level := record.Level
-	s := severity.InfoLog
-	switch {
-	case level >= slog.LevelError:
-		s = severity.ErrorLog
-	case level >= slog.LevelWarn:
-		s = severity.WarningLog
-	}
-
-	var file string
-	var line int
-	if record.PC != 0 {
-		// Same as https://cs.opensource.google/go/x/exp/+/642cacee:slog/record.go;drc=642cacee5cc05231f45555a333d07f1005ffc287;l=70
-		fs := runtime.CallersFrames([]uintptr{record.PC})
-		f, _ := fs.Next()
-		if f.File != "" {
-			file = f.File
-			if slash := strings.LastIndex(file, "/"); slash >= 0 {
-				file = file[slash+1:]
-			}
-			line = f.Line
-		}
-	} else {
-		file = "???"
-		line = 1
-	}
-
-	kvList := make([]interface{}, 0, 2*record.NumAttrs())
-	record.Attrs(func(attr slog.Attr) bool {
-		kvList = appendAttr(groups, kvList, attr)
-		return true
-	})
-
-	printWithInfos(file, line, now, nil, s, record.Message, kvList)
-	return nil
-}
-
-func Attrs2KVList(groups string, attrs []slog.Attr) []interface{} {
-	kvList := make([]interface{}, 0, 2*len(attrs))
-	for _, attr := range attrs {
-		kvList = appendAttr(groups, kvList, attr)
-	}
-	return kvList
-}
-
-func appendAttr(groups string, kvList []interface{}, attr slog.Attr) []interface{} {
-	var key string
-	if groups != "" {
-		key = groups + "." + attr.Key
-	} else {
-		key = attr.Key
-	}
-	return append(kvList, key, attr.Value)
-}
diff --git a/vendor/k8s.io/klog/v2/k8s_references.go b/vendor/k8s.io/klog/v2/k8s_references.go
deleted file mode 100644
index 786af74bf..000000000
--- a/vendor/k8s.io/klog/v2/k8s_references.go
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
-Copyright 2021 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package klog
-
-import (
-	"bytes"
-	"fmt"
-	"reflect"
-	"strings"
-
-	"github.com/go-logr/logr"
-)
-
-// ObjectRef references a kubernetes object
-type ObjectRef struct {
-	Name      string `json:"name"`
-	Namespace string `json:"namespace,omitempty"`
-}
-
-func (ref ObjectRef) String() string {
-	if ref.Namespace != "" {
-		var builder strings.Builder
-		builder.Grow(len(ref.Namespace) + len(ref.Name) + 1)
-		builder.WriteString(ref.Namespace)
-		builder.WriteRune('/')
-		builder.WriteString(ref.Name)
-		return builder.String()
-	}
-	return ref.Name
-}
-
-func (ref ObjectRef) WriteText(out *bytes.Buffer) {
-	out.WriteRune('"')
-	ref.writeUnquoted(out)
-	out.WriteRune('"')
-}
-
-func (ref ObjectRef) writeUnquoted(out *bytes.Buffer) {
-	if ref.Namespace != "" {
-		out.WriteString(ref.Namespace)
-		out.WriteRune('/')
-	}
-	out.WriteString(ref.Name)
-}
-
-// MarshalLog ensures that loggers with support for structured output will log
-// as a struct by removing the String method via a custom type.
-func (ref ObjectRef) MarshalLog() interface{} {
-	type or ObjectRef
-	return or(ref)
-}
-
-var _ logr.Marshaler = ObjectRef{}
-
-// KMetadata is a subset of the kubernetes k8s.io/apimachinery/pkg/apis/meta/v1.Object interface
-// this interface may expand in the future, but will always be a subset of the
-// kubernetes k8s.io/apimachinery/pkg/apis/meta/v1.Object interface
-type KMetadata interface {
-	GetName() string
-	GetNamespace() string
-}
-
-// KObj returns ObjectRef from ObjectMeta
-func KObj(obj KMetadata) ObjectRef {
-	if obj == nil {
-		return ObjectRef{}
-	}
-	if val := reflect.ValueOf(obj); val.Kind() == reflect.Ptr && val.IsNil() {
-		return ObjectRef{}
-	}
-
-	return ObjectRef{
-		Name:      obj.GetName(),
-		Namespace: obj.GetNamespace(),
-	}
-}
-
-// KRef returns ObjectRef from name and namespace
-func KRef(namespace, name string) ObjectRef {
-	return ObjectRef{
-		Name:      name,
-		Namespace: namespace,
-	}
-}
-
-// KObjs returns slice of ObjectRef from an slice of ObjectMeta
-//
-// DEPRECATED: Use KObjSlice instead, it has better performance.
-func KObjs(arg interface{}) []ObjectRef {
-	s := reflect.ValueOf(arg)
-	if s.Kind() != reflect.Slice {
-		return nil
-	}
-	objectRefs := make([]ObjectRef, 0, s.Len())
-	for i := 0; i < s.Len(); i++ {
-		if v, ok := s.Index(i).Interface().(KMetadata); ok {
-			objectRefs = append(objectRefs, KObj(v))
-		} else {
-			return nil
-		}
-	}
-	return objectRefs
-}
-
-// KObjSlice takes a slice of objects that implement the KMetadata interface
-// and returns an object that gets logged as a slice of ObjectRef values or a
-// string containing those values, depending on whether the logger prefers text
-// output or structured output.
-//
-// An error string is logged when KObjSlice is not passed a suitable slice.
-//
-// Processing of the argument is delayed until the value actually gets logged,
-// in contrast to KObjs where that overhead is incurred regardless of whether
-// the result is needed.
-func KObjSlice(arg interface{}) interface{} {
-	return kobjSlice{arg: arg}
-}
-
-type kobjSlice struct {
-	arg interface{}
-}
-
-var _ fmt.Stringer = kobjSlice{}
-var _ logr.Marshaler = kobjSlice{}
-
-func (ks kobjSlice) String() string {
-	objectRefs, errStr := ks.process()
-	if errStr != "" {
-		return errStr
-	}
-	return fmt.Sprintf("%v", objectRefs)
-}
-
-func (ks kobjSlice) MarshalLog() interface{} {
-	objectRefs, errStr := ks.process()
-	if errStr != "" {
-		return errStr
-	}
-	return objectRefs
-}
-
-func (ks kobjSlice) process() (objs []interface{}, err string) {
-	s := reflect.ValueOf(ks.arg)
-	switch s.Kind() {
-	case reflect.Invalid:
-		// nil parameter, print as nil.
-		return nil, ""
-	case reflect.Slice:
-		// Okay, handle below.
-	default:
-		return nil, fmt.Sprintf("<KObjSlice needs a slice, got type %T>", ks.arg)
-	}
-	objectRefs := make([]interface{}, 0, s.Len())
-	for i := 0; i < s.Len(); i++ {
-		item := s.Index(i).Interface()
-		if item == nil {
-			objectRefs = append(objectRefs, nil)
-		} else if v, ok := item.(KMetadata); ok {
-			objectRefs = append(objectRefs, KObj(v))
-		} else {
-			return nil, fmt.Sprintf("<KObjSlice needs a slice of values implementing KMetadata, got type %T>", item)
-		}
-	}
-	return objectRefs, ""
-}
-
-var nilToken = []byte("null")
-
-func (ks kobjSlice) WriteText(out *bytes.Buffer) {
-	s := reflect.ValueOf(ks.arg)
-	switch s.Kind() {
-	case reflect.Invalid:
-		// nil parameter, print as null.
-		out.Write(nilToken)
-		return
-	case reflect.Slice:
-		// Okay, handle below.
-	default:
-		fmt.Fprintf(out, `"<KObjSlice needs a slice, got type %T>"`, ks.arg)
-		return
-	}
-	out.Write([]byte{'['})
-	defer out.Write([]byte{']'})
-	for i := 0; i < s.Len(); i++ {
-		if i > 0 {
-			out.Write([]byte{','})
-		}
-		item := s.Index(i).Interface()
-		if item == nil {
-			out.Write(nilToken)
-		} else if v, ok := item.(KMetadata); ok {
-			KObj(v).WriteText(out)
-		} else {
-			fmt.Fprintf(out, `"<KObjSlice needs a slice of values implementing KMetadata, got type %T>"`, item)
-			return
-		}
-	}
-}
diff --git a/vendor/k8s.io/klog/v2/k8s_references_slog.go b/vendor/k8s.io/klog/v2/k8s_references_slog.go
deleted file mode 100644
index 5522c84c7..000000000
--- a/vendor/k8s.io/klog/v2/k8s_references_slog.go
+++ /dev/null
@@ -1,39 +0,0 @@
-//go:build go1.21
-// +build go1.21
-
-/*
-Copyright 2021 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package klog
-
-import (
-	"log/slog"
-)
-
-func (ref ObjectRef) LogValue() slog.Value {
-	if ref.Namespace != "" {
-		return slog.GroupValue(slog.String("name", ref.Name), slog.String("namespace", ref.Namespace))
-	}
-	return slog.GroupValue(slog.String("name", ref.Name))
-}
-
-var _ slog.LogValuer = ObjectRef{}
-
-func (ks kobjSlice) LogValue() slog.Value {
-	return slog.AnyValue(ks.MarshalLog())
-}
-
-var _ slog.LogValuer = kobjSlice{}
diff --git a/vendor/k8s.io/klog/v2/klog.go b/vendor/k8s.io/klog/v2/klog.go
deleted file mode 100644
index 47ec9466a..000000000
--- a/vendor/k8s.io/klog/v2/klog.go
+++ /dev/null
@@ -1,1734 +0,0 @@
-// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/
-//
-// Copyright 2013 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package klog contains the following functionality:
-//
-//   - output routing as defined via command line flags ([InitFlags])
-//   - log formatting as text, either with a single, unstructured string ([Info], [Infof], etc.)
-//     or as a structured log entry with message and key/value pairs ([InfoS], etc.)
-//   - management of a go-logr [Logger] ([SetLogger], [Background], [TODO])
-//   - helper functions for logging values ([Format]) and managing the state of klog ([CaptureState], [State.Restore])
-//   - wrappers for [logr] APIs for contextual logging where the wrappers can
-//     be turned into no-ops ([EnableContextualLogging], [NewContext], [FromContext],
-//     [LoggerWithValues], [LoggerWithName]); if the ability to turn off
-//     contextual logging is not needed, then go-logr can also be used directly
-//   - type aliases for go-logr types to simplify imports in code which uses both (e.g. [Logger])
-//   - [k8s.io/klog/v2/textlogger]: a logger which uses the same formatting as klog log with
-//     simpler output routing; beware that it comes with its own command line flags
-//     and does not use the ones from klog
-//   - [k8s.io/klog/v2/ktesting]: per-test output in Go unit tests
-//   - [k8s.io/klog/v2/klogr]: a deprecated, standalone [logr.Logger] on top of the main klog package;
-//     use [Background] instead if klog output routing is needed, [k8s.io/klog/v2/textlogger] if not
-//   - [k8s.io/klog/v2/examples]: demos of this functionality
-//   - [k8s.io/klog/v2/test]: reusable tests for [logr.Logger] implementations
-//
-// Basic examples:
-//
-//	klog.Info("Prepare to repel boarders")
-//
-//	klog.Fatalf("Initialization failed: %s", err)
-//
-// See the documentation for the V function for an explanation of these examples:
-//
-//	if klog.V(2) {
-//		klog.Info("Starting transaction...")
-//	}
-//
-//	klog.V(2).Infoln("Processed", nItems, "elements")
-//
-// Log output is buffered and written periodically using Flush. Programs
-// should call Flush before exiting to guarantee all log output is written.
-//
-// By default, all log statements write to standard error.
-// This package provides several flags that modify this behavior.
-// As a result, flag.Parse must be called before any logging is done.
-//
-//		-logtostderr=true
-//			Logs are written to standard error instead of to files.
-//	             This shortcuts most of the usual output routing:
-//	             -alsologtostderr, -stderrthreshold and -log_dir have no
-//	             effect and output redirection at runtime with SetOutput is
-//	             ignored.
-//		-alsologtostderr=false
-//			Logs are written to standard error as well as to files.
-//		-stderrthreshold=ERROR
-//			Log events at or above this severity are logged to standard
-//			error as well as to files.
-//		-log_dir=""
-//			Log files will be written to this directory instead of the
-//			default temporary directory.
-//
-//		Other flags provide aids to debugging.
-//
-//		-log_backtrace_at=""
-//			When set to a file and line number holding a logging statement,
-//			such as
-//				-log_backtrace_at=gopherflakes.go:234
-//			a stack trace will be written to the Info log whenever execution
-//			hits that statement. (Unlike with -vmodule, the ".go" must be
-//			present.)
-//		-v=0
-//			Enable V-leveled logging at the specified level.
-//		-vmodule=""
-//			The syntax of the argument is a comma-separated list of pattern=N,
-//			where pattern is a literal file name (minus the ".go" suffix) or
-//			"glob" pattern and N is a V level. For instance,
-//				-vmodule=gopher*=3
-//			sets the V level to 3 in all Go files whose names begin "gopher".
-package klog
-
-import (
-	"bufio"
-	"bytes"
-	"errors"
-	"flag"
-	"fmt"
-	"io"
-	stdLog "log"
-	"math"
-	"os"
-	"path/filepath"
-	"runtime"
-	"strconv"
-	"strings"
-	"sync"
-	"sync/atomic"
-	"time"
-
-	"k8s.io/klog/v2/internal/buffer"
-	"k8s.io/klog/v2/internal/clock"
-	"k8s.io/klog/v2/internal/dbg"
-	"k8s.io/klog/v2/internal/serialize"
-	"k8s.io/klog/v2/internal/severity"
-)
-
-// severityValue identifies the sort of log: info, warning etc. It also implements
-// the flag.Value interface. The -stderrthreshold flag is of type severity and
-// should be modified only through the flag.Value interface. The values match
-// the corresponding constants in C++.
-type severityValue struct {
-	severity.Severity
-}
-
-// get returns the value of the severity.
-func (s *severityValue) get() severity.Severity {
-	return severity.Severity(atomic.LoadInt32((*int32)(&s.Severity)))
-}
-
-// set sets the value of the severity.
-func (s *severityValue) set(val severity.Severity) {
-	atomic.StoreInt32((*int32)(&s.Severity), int32(val))
-}
-
-// String is part of the flag.Value interface.
-func (s *severityValue) String() string {
-	return strconv.FormatInt(int64(s.Severity), 10)
-}
-
-// Get is part of the flag.Getter interface.
-func (s *severityValue) Get() interface{} {
-	return s.Severity
-}
-
-// Set is part of the flag.Value interface.
-func (s *severityValue) Set(value string) error {
-	var threshold severity.Severity
-	// Is it a known name?
-	if v, ok := severity.ByName(value); ok {
-		threshold = v
-	} else {
-		v, err := strconv.ParseInt(value, 10, 32)
-		if err != nil {
-			return err
-		}
-		threshold = severity.Severity(v)
-	}
-	logging.stderrThreshold.set(threshold)
-	return nil
-}
-
-// OutputStats tracks the number of output lines and bytes written.
-type OutputStats struct {
-	lines int64
-	bytes int64
-}
-
-// Lines returns the number of lines written.
-func (s *OutputStats) Lines() int64 {
-	return atomic.LoadInt64(&s.lines)
-}
-
-// Bytes returns the number of bytes written.
-func (s *OutputStats) Bytes() int64 {
-	return atomic.LoadInt64(&s.bytes)
-}
-
-// Stats tracks the number of lines of output and number of bytes
-// per severity level. Values must be read with atomic.LoadInt64.
-var Stats struct {
-	Info, Warning, Error OutputStats
-}
-
-var severityStats = [severity.NumSeverity]*OutputStats{
-	severity.InfoLog:    &Stats.Info,
-	severity.WarningLog: &Stats.Warning,
-	severity.ErrorLog:   &Stats.Error,
-}
-
-// Level is exported because it appears in the arguments to V and is
-// the type of the v flag, which can be set programmatically.
-// It's a distinct type because we want to discriminate it from logType.
-// Variables of type level are only changed under logging.mu.
-// The -v flag is read only with atomic ops, so the state of the logging
-// module is consistent.
-
-// Level is treated as a sync/atomic int32.
-
-// Level specifies a level of verbosity for V logs. *Level implements
-// flag.Value; the -v flag is of type Level and should be modified
-// only through the flag.Value interface.
-type Level int32
-
-// get returns the value of the Level.
-func (l *Level) get() Level {
-	return Level(atomic.LoadInt32((*int32)(l)))
-}
-
-// set sets the value of the Level.
-func (l *Level) set(val Level) {
-	atomic.StoreInt32((*int32)(l), int32(val))
-}
-
-// String is part of the flag.Value interface.
-func (l *Level) String() string {
-	return strconv.FormatInt(int64(*l), 10)
-}
-
-// Get is part of the flag.Getter interface.
-func (l *Level) Get() interface{} {
-	return *l
-}
-
-// Set is part of the flag.Value interface.
-func (l *Level) Set(value string) error {
-	v, err := strconv.ParseInt(value, 10, 32)
-	if err != nil {
-		return err
-	}
-	logging.mu.Lock()
-	defer logging.mu.Unlock()
-	logging.setVState(Level(v), logging.vmodule.filter, false)
-	return nil
-}
-
-// moduleSpec represents the setting of the -vmodule flag.
-type moduleSpec struct {
-	filter []modulePat
-}
-
-// modulePat contains a filter for the -vmodule flag.
-// It holds a verbosity level and a file pattern to match.
-type modulePat struct {
-	pattern string
-	literal bool // The pattern is a literal string
-	level   Level
-}
-
-// match reports whether the file matches the pattern. It uses a string
-// comparison if the pattern contains no metacharacters.
-func (m *modulePat) match(file string) bool {
-	if m.literal {
-		return file == m.pattern
-	}
-	match, _ := filepath.Match(m.pattern, file)
-	return match
-}
-
-func (m *moduleSpec) String() string {
-	// Lock because the type is not atomic. TODO: clean this up.
-	logging.mu.Lock()
-	defer logging.mu.Unlock()
-	return m.serialize()
-}
-
-func (m *moduleSpec) serialize() string {
-	var b bytes.Buffer
-	for i, f := range m.filter {
-		if i > 0 {
-			b.WriteRune(',')
-		}
-		fmt.Fprintf(&b, "%s=%d", f.pattern, f.level)
-	}
-	return b.String()
-}
-
-// Get is part of the (Go 1.2)  flag.Getter interface. It always returns nil for this flag type since the
-// struct is not exported.
-func (m *moduleSpec) Get() interface{} {
-	return nil
-}
-
-var errVmoduleSyntax = errors.New("syntax error: expect comma-separated list of filename=N")
-
-// Set will sets module value
-// Syntax: -vmodule=recordio=2,file=1,gfs*=3
-func (m *moduleSpec) Set(value string) error {
-	filter, err := parseModuleSpec(value)
-	if err != nil {
-		return err
-	}
-	logging.mu.Lock()
-	defer logging.mu.Unlock()
-	logging.setVState(logging.verbosity, filter, true)
-	return nil
-}
-
-func parseModuleSpec(value string) ([]modulePat, error) {
-	var filter []modulePat
-	for _, pat := range strings.Split(value, ",") {
-		if len(pat) == 0 {
-			// Empty strings such as from a trailing comma can be ignored.
-			continue
-		}
-		patLev := strings.Split(pat, "=")
-		if len(patLev) != 2 || len(patLev[0]) == 0 || len(patLev[1]) == 0 {
-			return nil, errVmoduleSyntax
-		}
-		pattern := patLev[0]
-		v, err := strconv.ParseInt(patLev[1], 10, 32)
-		if err != nil {
-			return nil, errors.New("syntax error: expect comma-separated list of filename=N")
-		}
-		if v < 0 {
-			return nil, errors.New("negative value for vmodule level")
-		}
-		if v == 0 {
-			continue // Ignore. It's harmless but no point in paying the overhead.
-		}
-		// TODO: check syntax of filter?
-		filter = append(filter, modulePat{pattern, isLiteral(pattern), Level(v)})
-	}
-	return filter, nil
-}
-
-// isLiteral reports whether the pattern is a literal string, that is, has no metacharacters
-// that require filepath.Match to be called to match the pattern.
-func isLiteral(pattern string) bool {
-	return !strings.ContainsAny(pattern, `\*?[]`)
-}
-
-// traceLocation represents the setting of the -log_backtrace_at flag.
-type traceLocation struct {
-	file string
-	line int
-}
-
-// isSet reports whether the trace location has been specified.
-// logging.mu is held.
-func (t *traceLocation) isSet() bool {
-	return t.line > 0
-}
-
-// match reports whether the specified file and line matches the trace location.
-// The argument file name is the full path, not the basename specified in the flag.
-// logging.mu is held.
-func (t *traceLocation) match(file string, line int) bool {
-	if t.line != line {
-		return false
-	}
-	if i := strings.LastIndex(file, "/"); i >= 0 {
-		file = file[i+1:]
-	}
-	return t.file == file
-}
-
-func (t *traceLocation) String() string {
-	// Lock because the type is not atomic. TODO: clean this up.
-	logging.mu.Lock()
-	defer logging.mu.Unlock()
-	return fmt.Sprintf("%s:%d", t.file, t.line)
-}
-
-// Get is part of the (Go 1.2) flag.Getter interface. It always returns nil for this flag type since the
-// struct is not exported
-func (t *traceLocation) Get() interface{} {
-	return nil
-}
-
-var errTraceSyntax = errors.New("syntax error: expect file.go:234")
-
-// Set will sets backtrace value
-// Syntax: -log_backtrace_at=gopherflakes.go:234
-// Note that unlike vmodule the file extension is included here.
-func (t *traceLocation) Set(value string) error {
-	if value == "" {
-		// Unset.
-		logging.mu.Lock()
-		defer logging.mu.Unlock()
-		t.line = 0
-		t.file = ""
-		return nil
-	}
-	fields := strings.Split(value, ":")
-	if len(fields) != 2 {
-		return errTraceSyntax
-	}
-	file, line := fields[0], fields[1]
-	if !strings.Contains(file, ".") {
-		return errTraceSyntax
-	}
-	v, err := strconv.Atoi(line)
-	if err != nil {
-		return errTraceSyntax
-	}
-	if v <= 0 {
-		return errors.New("negative or zero value for level")
-	}
-	logging.mu.Lock()
-	defer logging.mu.Unlock()
-	t.line = v
-	t.file = file
-	return nil
-}
-
-var logging loggingT
-var commandLine flag.FlagSet
-
-// init sets up the defaults and creates command line flags.
-func init() {
-	commandLine.StringVar(&logging.logDir, "log_dir", "", "If non-empty, write log files in this directory (no effect when -logtostderr=true)")
-	commandLine.StringVar(&logging.logFile, "log_file", "", "If non-empty, use this log file (no effect when -logtostderr=true)")
-	commandLine.Uint64Var(&logging.logFileMaxSizeMB, "log_file_max_size", 1800,
-		"Defines the maximum size a log file can grow to (no effect when -logtostderr=true). Unit is megabytes. "+
-			"If the value is 0, the maximum file size is unlimited.")
-	commandLine.BoolVar(&logging.toStderr, "logtostderr", true, "log to standard error instead of files")
-	commandLine.BoolVar(&logging.alsoToStderr, "alsologtostderr", false, "log to standard error as well as files (no effect when -logtostderr=true)")
-	logging.setVState(0, nil, false)
-	commandLine.Var(&logging.verbosity, "v", "number for the log level verbosity")
-	commandLine.BoolVar(&logging.addDirHeader, "add_dir_header", false, "If true, adds the file directory to the header of the log messages")
-	commandLine.BoolVar(&logging.skipHeaders, "skip_headers", false, "If true, avoid header prefixes in the log messages")
-	commandLine.BoolVar(&logging.oneOutput, "one_output", false, "If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true)")
-	commandLine.BoolVar(&logging.skipLogHeaders, "skip_log_headers", false, "If true, avoid headers when opening log files (no effect when -logtostderr=true)")
-	logging.stderrThreshold = severityValue{
-		Severity: severity.ErrorLog, // Default stderrThreshold is ERROR.
-	}
-	commandLine.Var(&logging.stderrThreshold, "stderrthreshold", "logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=true)")
-	commandLine.Var(&logging.vmodule, "vmodule", "comma-separated list of pattern=N settings for file-filtered logging")
-	commandLine.Var(&logging.traceLocation, "log_backtrace_at", "when logging hits line file:N, emit a stack trace")
-
-	logging.settings.contextualLoggingEnabled = true
-	logging.flushD = newFlushDaemon(logging.lockAndFlushAll, nil)
-}
-
-// InitFlags is for explicitly initializing the flags.
-// It may get called repeatedly for different flagsets, but not
-// twice for the same one. May get called concurrently
-// to other goroutines using klog. However, only some flags
-// may get set concurrently (see implementation).
-func InitFlags(flagset *flag.FlagSet) {
-	if flagset == nil {
-		flagset = flag.CommandLine
-	}
-
-	commandLine.VisitAll(func(f *flag.Flag) {
-		flagset.Var(f.Value, f.Name, f.Usage)
-	})
-}
-
-// Flush flushes all pending log I/O.
-func Flush() {
-	logging.lockAndFlushAll()
-}
-
-// settings collects global settings.
-type settings struct {
-	// contextualLoggingEnabled controls whether contextual logging is
-	// active. Disabling it may have some small performance benefit.
-	contextualLoggingEnabled bool
-
-	// logger is the global Logger chosen by users of klog, nil if
-	// none is available.
-	logger *logWriter
-
-	// loggerOptions contains the options that were supplied for
-	// globalLogger.
-	loggerOptions loggerOptions
-
-	// Boolean flags. Not handled atomically because the flag.Value interface
-	// does not let us avoid the =true, and that shorthand is necessary for
-	// compatibility. TODO: does this matter enough to fix? Seems unlikely.
-	toStderr     bool // The -logtostderr flag.
-	alsoToStderr bool // The -alsologtostderr flag.
-
-	// Level flag. Handled atomically.
-	stderrThreshold severityValue // The -stderrthreshold flag.
-
-	// Access to all of the following fields must be protected via a mutex.
-
-	// file holds writer for each of the log types.
-	file [severity.NumSeverity]io.Writer
-	// flushInterval is the interval for periodic flushing. If zero,
-	// the global default will be used.
-	flushInterval time.Duration
-
-	// filterLength stores the length of the vmodule filter chain. If greater
-	// than zero, it means vmodule is enabled. It may be read safely
-	// using sync.LoadInt32, but is only modified under mu.
-	filterLength int32
-	// traceLocation is the state of the -log_backtrace_at flag.
-	traceLocation traceLocation
-	// These flags are modified only under lock, although verbosity may be fetched
-	// safely using atomic.LoadInt32.
-	vmodule   moduleSpec // The state of the -vmodule flag.
-	verbosity Level      // V logging level, the value of the -v flag/
-
-	// If non-empty, overrides the choice of directory in which to write logs.
-	// See createLogDirs for the full list of possible destinations.
-	logDir string
-
-	// If non-empty, specifies the path of the file to write logs. mutually exclusive
-	// with the log_dir option.
-	logFile string
-
-	// When logFile is specified, this limiter makes sure the logFile won't exceeds a certain size. When exceeds, the
-	// logFile will be cleaned up. If this value is 0, no size limitation will be applied to logFile.
-	logFileMaxSizeMB uint64
-
-	// If true, do not add the prefix headers, useful when used with SetOutput
-	skipHeaders bool
-
-	// If true, do not add the headers to log files
-	skipLogHeaders bool
-
-	// If true, add the file directory to the header
-	addDirHeader bool
-
-	// If true, messages will not be propagated to lower severity log levels
-	oneOutput bool
-
-	// If set, all output will be filtered through the filter.
-	filter LogFilter
-}
-
-// deepCopy creates a copy that doesn't share anything with the original
-// instance.
-func (s settings) deepCopy() settings {
-	// vmodule is a slice and would be shared, so we have copy it.
-	filter := make([]modulePat, len(s.vmodule.filter))
-	copy(filter, s.vmodule.filter)
-	s.vmodule.filter = filter
-
-	if s.logger != nil {
-		logger := *s.logger
-		s.logger = &logger
-	}
-
-	return s
-}
-
-// loggingT collects all the global state of the logging setup.
-type loggingT struct {
-	settings
-
-	// flushD holds a flushDaemon that frequently flushes log file buffers.
-	// Uses its own mutex.
-	flushD *flushDaemon
-
-	// mu protects the remaining elements of this structure and the fields
-	// in settingsT which need a mutex lock.
-	mu sync.Mutex
-
-	// pcs is used in V to avoid an allocation when computing the caller's PC.
-	pcs [1]uintptr
-	// vmap is a cache of the V Level for each V() call site, identified by PC.
-	// It is wiped whenever the vmodule flag changes state.
-	vmap map[uintptr]Level
-}
-
-// setVState sets a consistent state for V logging.
-// l.mu is held.
-func (l *loggingT) setVState(verbosity Level, filter []modulePat, setFilter bool) {
-	// Turn verbosity off so V will not fire while we are in transition.
-	l.verbosity.set(0)
-	// Ditto for filter length.
-	atomic.StoreInt32(&l.filterLength, 0)
-
-	// Set the new filters and wipe the pc->Level map if the filter has changed.
-	if setFilter {
-		l.vmodule.filter = filter
-		l.vmap = make(map[uintptr]Level)
-	}
-
-	// Things are consistent now, so enable filtering and verbosity.
-	// They are enabled in order opposite to that in V.
-	atomic.StoreInt32(&l.filterLength, int32(len(filter)))
-	l.verbosity.set(verbosity)
-}
-
-var timeNow = time.Now // Stubbed out for testing.
-
-// CaptureState gathers information about all current klog settings.
-// The result can be used to restore those settings.
-func CaptureState() State {
-	logging.mu.Lock()
-	defer logging.mu.Unlock()
-	return &state{
-		settings:      logging.settings.deepCopy(),
-		flushDRunning: logging.flushD.isRunning(),
-		maxSize:       MaxSize,
-	}
-}
-
-// State stores a snapshot of klog settings. It gets created with CaptureState
-// and can be used to restore the entire state. Modifying individual settings
-// is supported via the command line flags.
-type State interface {
-	// Restore restore the entire state. It may get called more than once.
-	Restore()
-}
-
-type state struct {
-	settings
-
-	flushDRunning bool
-	maxSize       uint64
-}
-
-func (s *state) Restore() {
-	// This needs to be done before mutex locking.
-	if s.flushDRunning && !logging.flushD.isRunning() {
-		// This is not quite accurate: StartFlushDaemon might
-		// have been called with some different interval.
-		interval := s.flushInterval
-		if interval == 0 {
-			interval = flushInterval
-		}
-		logging.flushD.run(interval)
-	} else if !s.flushDRunning && logging.flushD.isRunning() {
-		logging.flushD.stop()
-	}
-
-	logging.mu.Lock()
-	defer logging.mu.Unlock()
-
-	logging.settings = s.settings
-	logging.setVState(s.verbosity, s.vmodule.filter, true)
-	MaxSize = s.maxSize
-}
-
-/*
-header formats a log header as defined by the C++ implementation.
-It returns a buffer containing the formatted header and the user's file and line number.
-The depth specifies how many stack frames above lives the source line to be identified in the log message.
-
-Log lines have this form:
-
-	Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg...
-
-where the fields are defined as follows:
-
-	L                A single character, representing the log level (eg 'I' for INFO)
-	mm               The month (zero padded; ie May is '05')
-	dd               The day (zero padded)
-	hh:mm:ss.uuuuuu  Time in hours, minutes and fractional seconds
-	threadid         The space-padded thread ID as returned by GetTID()
-	file             The file name
-	line             The line number
-	msg              The user-supplied message
-*/
-func (l *loggingT) header(s severity.Severity, depth int) (*buffer.Buffer, string, int) {
-	_, file, line, ok := runtime.Caller(3 + depth)
-	if !ok {
-		file = "???"
-		line = 1
-	} else {
-		if slash := strings.LastIndex(file, "/"); slash >= 0 {
-			path := file
-			file = path[slash+1:]
-			if l.addDirHeader {
-				if dirsep := strings.LastIndex(path[:slash], "/"); dirsep >= 0 {
-					file = path[dirsep+1:]
-				}
-			}
-		}
-	}
-	return l.formatHeader(s, file, line, timeNow()), file, line
-}
-
-// formatHeader formats a log header using the provided file name and line number.
-func (l *loggingT) formatHeader(s severity.Severity, file string, line int, now time.Time) *buffer.Buffer {
-	buf := buffer.GetBuffer()
-	if l.skipHeaders {
-		return buf
-	}
-	buf.FormatHeader(s, file, line, now)
-	return buf
-}
-
-func (l *loggingT) println(s severity.Severity, logger *logWriter, filter LogFilter, args ...interface{}) {
-	l.printlnDepth(s, logger, filter, 1, args...)
-}
-
-func (l *loggingT) printlnDepth(s severity.Severity, logger *logWriter, filter LogFilter, depth int, args ...interface{}) {
-	if false {
-		_ = fmt.Sprintln(args...) // cause vet to treat this function like fmt.Println
-	}
-
-	buf, file, line := l.header(s, depth)
-	// If a logger is set and doesn't support writing a formatted buffer,
-	// we clear the generated header as we rely on the backing
-	// logger implementation to print headers.
-	if logger != nil && logger.writeKlogBuffer == nil {
-		buffer.PutBuffer(buf)
-		buf = buffer.GetBuffer()
-	}
-	if filter != nil {
-		args = filter.Filter(args)
-	}
-	fmt.Fprintln(buf, args...)
-	l.output(s, logger, buf, depth, file, line, false)
-}
-
-func (l *loggingT) print(s severity.Severity, logger *logWriter, filter LogFilter, args ...interface{}) {
-	l.printDepth(s, logger, filter, 1, args...)
-}
-
-func (l *loggingT) printDepth(s severity.Severity, logger *logWriter, filter LogFilter, depth int, args ...interface{}) {
-	if false {
-		_ = fmt.Sprint(args...) //  // cause vet to treat this function like fmt.Print
-	}
-
-	buf, file, line := l.header(s, depth)
-	l.printWithInfos(buf, file, line, s, logger, filter, depth+1, args...)
-}
-
-func (l *loggingT) printWithInfos(buf *buffer.Buffer, file string, line int, s severity.Severity, logger *logWriter, filter LogFilter, depth int, args ...interface{}) {
-	// If a logger is set and doesn't support writing a formatted buffer,
-	// we clear the generated header as we rely on the backing
-	// logger implementation to print headers.
-	if logger != nil && logger.writeKlogBuffer == nil {
-		buffer.PutBuffer(buf)
-		buf = buffer.GetBuffer()
-	}
-	if filter != nil {
-		args = filter.Filter(args)
-	}
-	fmt.Fprint(buf, args...)
-	if buf.Len() == 0 || buf.Bytes()[buf.Len()-1] != '\n' {
-		buf.WriteByte('\n')
-	}
-	l.output(s, logger, buf, depth, file, line, false)
-}
-
-func (l *loggingT) printf(s severity.Severity, logger *logWriter, filter LogFilter, format string, args ...interface{}) {
-	l.printfDepth(s, logger, filter, 1, format, args...)
-}
-
-func (l *loggingT) printfDepth(s severity.Severity, logger *logWriter, filter LogFilter, depth int, format string, args ...interface{}) {
-	if false {
-		_ = fmt.Sprintf(format, args...) // cause vet to treat this function like fmt.Printf
-	}
-
-	buf, file, line := l.header(s, depth)
-	// If a logger is set and doesn't support writing a formatted buffer,
-	// we clear the generated header as we rely on the backing
-	// logger implementation to print headers.
-	if logger != nil && logger.writeKlogBuffer == nil {
-		buffer.PutBuffer(buf)
-		buf = buffer.GetBuffer()
-	}
-	if filter != nil {
-		format, args = filter.FilterF(format, args)
-	}
-	fmt.Fprintf(buf, format, args...)
-	if buf.Bytes()[buf.Len()-1] != '\n' {
-		buf.WriteByte('\n')
-	}
-	l.output(s, logger, buf, depth, file, line, false)
-}
-
-// printWithFileLine behaves like print but uses the provided file and line number.  If
-// alsoLogToStderr is true, the log message always appears on standard error; it
-// will also appear in the log file unless --logtostderr is set.
-func (l *loggingT) printWithFileLine(s severity.Severity, logger *logWriter, filter LogFilter, file string, line int, alsoToStderr bool, args ...interface{}) {
-	buf := l.formatHeader(s, file, line, timeNow())
-	// If a logger is set and doesn't support writing a formatted buffer,
-	// we clear the generated header as we rely on the backing
-	// logger implementation to print headers.
-	if logger != nil && logger.writeKlogBuffer == nil {
-		buffer.PutBuffer(buf)
-		buf = buffer.GetBuffer()
-	}
-	if filter != nil {
-		args = filter.Filter(args)
-	}
-	fmt.Fprint(buf, args...)
-	if buf.Bytes()[buf.Len()-1] != '\n' {
-		buf.WriteByte('\n')
-	}
-	l.output(s, logger, buf, 2 /* depth */, file, line, alsoToStderr)
-}
-
-// if logger is specified, will call logger.Error, otherwise output with logging module.
-func (l *loggingT) errorS(err error, logger *logWriter, filter LogFilter, depth int, msg string, keysAndValues ...interface{}) {
-	if filter != nil {
-		msg, keysAndValues = filter.FilterS(msg, keysAndValues)
-	}
-	if logger != nil {
-		logger.WithCallDepth(depth+2).Error(err, msg, keysAndValues...)
-		return
-	}
-	l.printS(err, severity.ErrorLog, depth+1, msg, keysAndValues...)
-}
-
-// if logger is specified, will call logger.Info, otherwise output with logging module.
-func (l *loggingT) infoS(logger *logWriter, filter LogFilter, depth int, msg string, keysAndValues ...interface{}) {
-	if filter != nil {
-		msg, keysAndValues = filter.FilterS(msg, keysAndValues)
-	}
-	if logger != nil {
-		logger.WithCallDepth(depth+2).Info(msg, keysAndValues...)
-		return
-	}
-	l.printS(nil, severity.InfoLog, depth+1, msg, keysAndValues...)
-}
-
-// printS is called from infoS and errorS if logger is not specified.
-// set log severity by s
-func (l *loggingT) printS(err error, s severity.Severity, depth int, msg string, keysAndValues ...interface{}) {
-	// Only create a new buffer if we don't have one cached.
-	b := buffer.GetBuffer()
-	// The message is always quoted, even if it contains line breaks.
-	// If developers want multi-line output, they should use a small, fixed
-	// message and put the multi-line output into a value.
-	b.WriteString(strconv.Quote(msg))
-	if err != nil {
-		serialize.KVListFormat(&b.Buffer, "err", err)
-	}
-	serialize.KVListFormat(&b.Buffer, keysAndValues...)
-	l.printDepth(s, nil, nil, depth+1, &b.Buffer)
-	// Make the buffer available for reuse.
-	buffer.PutBuffer(b)
-}
-
-// SetOutput sets the output destination for all severities
-func SetOutput(w io.Writer) {
-	logging.mu.Lock()
-	defer logging.mu.Unlock()
-	for s := severity.FatalLog; s >= severity.InfoLog; s-- {
-		logging.file[s] = w
-	}
-}
-
-// SetOutputBySeverity sets the output destination for specific severity
-func SetOutputBySeverity(name string, w io.Writer) {
-	logging.mu.Lock()
-	defer logging.mu.Unlock()
-	sev, ok := severity.ByName(name)
-	if !ok {
-		panic(fmt.Sprintf("SetOutputBySeverity(%q): unrecognized severity name", name))
-	}
-	logging.file[sev] = w
-}
-
-// LogToStderr sets whether to log exclusively to stderr, bypassing outputs
-func LogToStderr(stderr bool) {
-	logging.mu.Lock()
-	defer logging.mu.Unlock()
-
-	logging.toStderr = stderr
-}
-
-// output writes the data to the log files and releases the buffer.
-func (l *loggingT) output(s severity.Severity, logger *logWriter, buf *buffer.Buffer, depth int, file string, line int, alsoToStderr bool) {
-	var isLocked = true
-	l.mu.Lock()
-	defer func() {
-		if isLocked {
-			// Unlock before returning in case that it wasn't done already.
-			l.mu.Unlock()
-		}
-	}()
-
-	if l.traceLocation.isSet() {
-		if l.traceLocation.match(file, line) {
-			buf.Write(dbg.Stacks(false))
-		}
-	}
-	data := buf.Bytes()
-	if logger != nil {
-		if logger.writeKlogBuffer != nil {
-			logger.writeKlogBuffer(data)
-		} else {
-			if len(data) > 0 && data[len(data)-1] == '\n' {
-				data = data[:len(data)-1]
-			}
-			// TODO: set 'severity' and caller information as structured log info
-			// keysAndValues := []interface{}{"severity", severityName[s], "file", file, "line", line}
-			if s == severity.ErrorLog {
-				logger.WithCallDepth(depth+3).Error(nil, string(data))
-			} else {
-				logger.WithCallDepth(depth + 3).Info(string(data))
-			}
-		}
-	} else if l.toStderr {
-		os.Stderr.Write(data)
-	} else {
-		if alsoToStderr || l.alsoToStderr || s >= l.stderrThreshold.get() {
-			os.Stderr.Write(data)
-		}
-
-		if logging.logFile != "" {
-			// Since we are using a single log file, all of the items in l.file array
-			// will point to the same file, so just use one of them to write data.
-			if l.file[severity.InfoLog] == nil {
-				if err := l.createFiles(severity.InfoLog); err != nil {
-					os.Stderr.Write(data) // Make sure the message appears somewhere.
-					l.exit(err)
-				}
-			}
-			_, _ = l.file[severity.InfoLog].Write(data)
-		} else {
-			if l.file[s] == nil {
-				if err := l.createFiles(s); err != nil {
-					os.Stderr.Write(data) // Make sure the message appears somewhere.
-					l.exit(err)
-				}
-			}
-
-			if l.oneOutput {
-				_, _ = l.file[s].Write(data)
-			} else {
-				switch s {
-				case severity.FatalLog:
-					_, _ = l.file[severity.FatalLog].Write(data)
-					fallthrough
-				case severity.ErrorLog:
-					_, _ = l.file[severity.ErrorLog].Write(data)
-					fallthrough
-				case severity.WarningLog:
-					_, _ = l.file[severity.WarningLog].Write(data)
-					fallthrough
-				case severity.InfoLog:
-					_, _ = l.file[severity.InfoLog].Write(data)
-				}
-			}
-		}
-	}
-	if s == severity.FatalLog {
-		// If we got here via Exit rather than Fatal, print no stacks.
-		if atomic.LoadUint32(&fatalNoStacks) > 0 {
-			l.mu.Unlock()
-			isLocked = false
-			timeoutFlush(ExitFlushTimeout)
-			OsExit(1)
-		}
-		// Dump all goroutine stacks before exiting.
-		// First, make sure we see the trace for the current goroutine on standard error.
-		// If -logtostderr has been specified, the loop below will do that anyway
-		// as the first stack in the full dump.
-		if !l.toStderr {
-			os.Stderr.Write(dbg.Stacks(false))
-		}
-
-		// Write the stack trace for all goroutines to the files.
-		trace := dbg.Stacks(true)
-		logExitFunc = func(error) {} // If we get a write error, we'll still exit below.
-		for log := severity.FatalLog; log >= severity.InfoLog; log-- {
-			if f := l.file[log]; f != nil { // Can be nil if -logtostderr is set.
-				_, _ = f.Write(trace)
-			}
-		}
-		l.mu.Unlock()
-		isLocked = false
-		timeoutFlush(ExitFlushTimeout)
-		OsExit(255) // C++ uses -1, which is silly because it's anded with 255 anyway.
-	}
-	buffer.PutBuffer(buf)
-
-	if stats := severityStats[s]; stats != nil {
-		atomic.AddInt64(&stats.lines, 1)
-		atomic.AddInt64(&stats.bytes, int64(len(data)))
-	}
-}
-
-// logExitFunc provides a simple mechanism to override the default behavior
-// of exiting on error. Used in testing and to guarantee we reach a required exit
-// for fatal logs. Instead, exit could be a function rather than a method but that
-// would make its use clumsier.
-var logExitFunc func(error)
-
-// exit is called if there is trouble creating or writing log files.
-// It flushes the logs and exits the program; there's no point in hanging around.
-// l.mu is held.
-func (l *loggingT) exit(err error) {
-	fmt.Fprintf(os.Stderr, "log: exiting because of error: %s\n", err)
-	// If logExitFunc is set, we do that instead of exiting.
-	if logExitFunc != nil {
-		logExitFunc(err)
-		return
-	}
-	needToSync := l.flushAll()
-	l.syncAll(needToSync)
-	OsExit(2)
-}
-
-// syncBuffer joins a bufio.Writer to its underlying file, providing access to the
-// file's Sync method and providing a wrapper for the Write method that provides log
-// file rotation. There are conflicting methods, so the file cannot be embedded.
-// l.mu is held for all its methods.
-type syncBuffer struct {
-	logger *loggingT
-	*bufio.Writer
-	file     *os.File
-	sev      severity.Severity
-	nbytes   uint64 // The number of bytes written to this file
-	maxbytes uint64 // The max number of bytes this syncBuffer.file can hold before cleaning up.
-}
-
-// CalculateMaxSize returns the real max size in bytes after considering the default max size and the flag options.
-func CalculateMaxSize() uint64 {
-	if logging.logFile != "" {
-		if logging.logFileMaxSizeMB == 0 {
-			// If logFileMaxSizeMB is zero, we don't have limitations on the log size.
-			return math.MaxUint64
-		}
-		// Flag logFileMaxSizeMB is in MB for user convenience.
-		return logging.logFileMaxSizeMB * 1024 * 1024
-	}
-	// If "log_file" flag is not specified, the target file (sb.file) will be cleaned up when reaches a fixed size.
-	return MaxSize
-}
-
-func (sb *syncBuffer) Write(p []byte) (n int, err error) {
-	if sb.nbytes+uint64(len(p)) >= sb.maxbytes {
-		if err := sb.rotateFile(time.Now(), false); err != nil {
-			sb.logger.exit(err)
-		}
-	}
-	n, err = sb.Writer.Write(p)
-	sb.nbytes += uint64(n)
-	if err != nil {
-		sb.logger.exit(err)
-	}
-	return
-}
-
-// rotateFile closes the syncBuffer's file and starts a new one.
-// The startup argument indicates whether this is the initial startup of klog.
-// If startup is true, existing files are opened for appending instead of truncated.
-func (sb *syncBuffer) rotateFile(now time.Time, startup bool) error {
-	if sb.file != nil {
-		sb.Flush()
-		sb.file.Close()
-	}
-	var err error
-	sb.file, _, err = create(severity.Name[sb.sev], now, startup)
-	if err != nil {
-		return err
-	}
-	if startup {
-		fileInfo, err := sb.file.Stat()
-		if err != nil {
-			return fmt.Errorf("file stat could not get fileinfo: %v", err)
-		}
-		// init file size
-		sb.nbytes = uint64(fileInfo.Size())
-	} else {
-		sb.nbytes = 0
-	}
-	sb.Writer = bufio.NewWriterSize(sb.file, bufferSize)
-
-	if sb.logger.skipLogHeaders {
-		return nil
-	}
-
-	// Write header.
-	var buf bytes.Buffer
-	fmt.Fprintf(&buf, "Log file created at: %s\n", now.Format("2006/01/02 15:04:05"))
-	fmt.Fprintf(&buf, "Running on machine: %s\n", host)
-	fmt.Fprintf(&buf, "Binary: Built with %s %s for %s/%s\n", runtime.Compiler, runtime.Version(), runtime.GOOS, runtime.GOARCH)
-	fmt.Fprintf(&buf, "Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg\n")
-	n, err := sb.file.Write(buf.Bytes())
-	sb.nbytes += uint64(n)
-	return err
-}
-
-// bufferSize sizes the buffer associated with each log file. It's large
-// so that log records can accumulate without the logging thread blocking
-// on disk I/O. The flushDaemon will block instead.
-const bufferSize = 256 * 1024
-
-// createFiles creates all the log files for severity from sev down to infoLog.
-// l.mu is held.
-func (l *loggingT) createFiles(sev severity.Severity) error {
-	interval := l.flushInterval
-	if interval == 0 {
-		interval = flushInterval
-	}
-	l.flushD.run(interval)
-	now := time.Now()
-	// Files are created in decreasing severity order, so as soon as we find one
-	// has already been created, we can stop.
-	for s := sev; s >= severity.InfoLog && l.file[s] == nil; s-- {
-		sb := &syncBuffer{
-			logger:   l,
-			sev:      s,
-			maxbytes: CalculateMaxSize(),
-		}
-		if err := sb.rotateFile(now, true); err != nil {
-			return err
-		}
-		l.file[s] = sb
-	}
-	return nil
-}
-
-const flushInterval = 5 * time.Second
-
-// flushDaemon periodically flushes the log file buffers.
-type flushDaemon struct {
-	mu       sync.Mutex
-	clock    clock.Clock
-	flush    func()
-	stopC    chan struct{}
-	stopDone chan struct{}
-}
-
-// newFlushDaemon returns a new flushDaemon. If the passed clock is nil, a
-// clock.RealClock is used.
-func newFlushDaemon(flush func(), tickClock clock.Clock) *flushDaemon {
-	if tickClock == nil {
-		tickClock = clock.RealClock{}
-	}
-	return &flushDaemon{
-		flush: flush,
-		clock: tickClock,
-	}
-}
-
-// run starts a goroutine that periodically calls the daemons flush function.
-// Calling run on an already running daemon will have no effect.
-func (f *flushDaemon) run(interval time.Duration) {
-	f.mu.Lock()
-	defer f.mu.Unlock()
-
-	if f.stopC != nil { // daemon already running
-		return
-	}
-
-	f.stopC = make(chan struct{}, 1)
-	f.stopDone = make(chan struct{}, 1)
-
-	ticker := f.clock.NewTicker(interval)
-	go func() {
-		defer ticker.Stop()
-		defer func() { f.stopDone <- struct{}{} }()
-		for {
-			select {
-			case <-ticker.C():
-				f.flush()
-			case <-f.stopC:
-				f.flush()
-				return
-			}
-		}
-	}()
-}
-
-// stop stops the running flushDaemon and waits until the daemon has shut down.
-// Calling stop on a daemon that isn't running will have no effect.
-func (f *flushDaemon) stop() {
-	f.mu.Lock()
-	defer f.mu.Unlock()
-
-	if f.stopC == nil { // daemon not running
-		return
-	}
-
-	f.stopC <- struct{}{}
-	<-f.stopDone
-
-	f.stopC = nil
-	f.stopDone = nil
-}
-
-// isRunning returns true if the flush daemon is running.
-func (f *flushDaemon) isRunning() bool {
-	f.mu.Lock()
-	defer f.mu.Unlock()
-	return f.stopC != nil
-}
-
-// StopFlushDaemon stops the flush daemon, if running, and flushes once.
-// This prevents klog from leaking goroutines on shutdown. After stopping
-// the daemon, you can still manually flush buffers again by calling Flush().
-func StopFlushDaemon() {
-	logging.flushD.stop()
-}
-
-// StartFlushDaemon ensures that the flush daemon runs with the given delay
-// between flush calls. If it is already running, it gets restarted.
-func StartFlushDaemon(interval time.Duration) {
-	StopFlushDaemon()
-	logging.flushD.run(interval)
-}
-
-// lockAndFlushAll is like flushAll but locks l.mu first.
-func (l *loggingT) lockAndFlushAll() {
-	l.mu.Lock()
-	needToSync := l.flushAll()
-	l.mu.Unlock()
-	// Some environments are slow when syncing and holding the lock might cause contention.
-	l.syncAll(needToSync)
-}
-
-// flushAll flushes all the logs
-// l.mu is held.
-//
-// The result is the number of files which need to be synced and the pointers to them.
-func (l *loggingT) flushAll() fileArray {
-	var needToSync fileArray
-
-	// Flush from fatal down, in case there's trouble flushing.
-	for s := severity.FatalLog; s >= severity.InfoLog; s-- {
-		file := l.file[s]
-		if sb, ok := file.(*syncBuffer); ok && sb.file != nil {
-			_ = sb.Flush() // ignore error
-			needToSync.files[needToSync.num] = sb.file
-			needToSync.num++
-		}
-	}
-	if logging.loggerOptions.flush != nil {
-		logging.loggerOptions.flush()
-	}
-	return needToSync
-}
-
-type fileArray struct {
-	num   int
-	files [severity.NumSeverity]*os.File
-}
-
-// syncAll attempts to "sync" their data to disk.
-func (l *loggingT) syncAll(needToSync fileArray) {
-	// Flush from fatal down, in case there's trouble flushing.
-	for i := 0; i < needToSync.num; i++ {
-		_ = needToSync.files[i].Sync() // ignore error
-	}
-}
-
-// CopyStandardLogTo arranges for messages written to the Go "log" package's
-// default logs to also appear in the Google logs for the named and lower
-// severities.  Subsequent changes to the standard log's default output location
-// or format may break this behavior.
-//
-// Valid names are "INFO", "WARNING", "ERROR", and "FATAL".  If the name is not
-// recognized, CopyStandardLogTo panics.
-func CopyStandardLogTo(name string) {
-	sev, ok := severity.ByName(name)
-	if !ok {
-		panic(fmt.Sprintf("log.CopyStandardLogTo(%q): unrecognized severity name", name))
-	}
-	// Set a log format that captures the user's file and line:
-	//   d.go:23: message
-	stdLog.SetFlags(stdLog.Lshortfile)
-	stdLog.SetOutput(logBridge(sev))
-}
-
-// NewStandardLogger returns a Logger that writes to the klog logs for the
-// named and lower severities.
-//
-// Valid names are "INFO", "WARNING", "ERROR", and "FATAL". If the name is not
-// recognized, NewStandardLogger panics.
-func NewStandardLogger(name string) *stdLog.Logger {
-	sev, ok := severity.ByName(name)
-	if !ok {
-		panic(fmt.Sprintf("klog.NewStandardLogger(%q): unknown severity", name))
-	}
-	return stdLog.New(logBridge(sev), "", stdLog.Lshortfile)
-}
-
-// logBridge provides the Write method that enables CopyStandardLogTo to connect
-// Go's standard logs to the logs provided by this package.
-type logBridge severity.Severity
-
-// Write parses the standard logging line and passes its components to the
-// logger for severity(lb).
-func (lb logBridge) Write(b []byte) (n int, err error) {
-	var (
-		file = "???"
-		line = 1
-		text string
-	)
-	// Split "d.go:23: message" into "d.go", "23", and "message".
-	if parts := bytes.SplitN(b, []byte{':'}, 3); len(parts) != 3 || len(parts[0]) < 1 || len(parts[2]) < 1 {
-		text = fmt.Sprintf("bad log format: %s", b)
-	} else {
-		file = string(parts[0])
-		text = string(parts[2][1:]) // skip leading space
-		line, err = strconv.Atoi(string(parts[1]))
-		if err != nil {
-			text = fmt.Sprintf("bad line number: %s", b)
-			line = 1
-		}
-	}
-	// printWithFileLine with alsoToStderr=true, so standard log messages
-	// always appear on standard error.
-	logging.printWithFileLine(severity.Severity(lb), logging.logger, logging.filter, file, line, true, text)
-	return len(b), nil
-}
-
-// setV computes and remembers the V level for a given PC
-// when vmodule is enabled.
-// File pattern matching takes the basename of the file, stripped
-// of its .go suffix, and uses filepath.Match, which is a little more
-// general than the *? matching used in C++.
-// l.mu is held.
-func (l *loggingT) setV(pc uintptr) Level {
-	fn := runtime.FuncForPC(pc)
-	file, _ := fn.FileLine(pc)
-	// The file is something like /a/b/c/d.go. We want just the d.
-	file = strings.TrimSuffix(file, ".go")
-	if slash := strings.LastIndex(file, "/"); slash >= 0 {
-		file = file[slash+1:]
-	}
-	for _, filter := range l.vmodule.filter {
-		if filter.match(file) {
-			l.vmap[pc] = filter.level
-			return filter.level
-		}
-	}
-	l.vmap[pc] = 0
-	return 0
-}
-
-// Verbose is a boolean type that implements Infof (like Printf) etc.
-// See the documentation of V for more information.
-type Verbose struct {
-	enabled bool
-	logger  *logWriter
-}
-
-func newVerbose(level Level, b bool) Verbose {
-	if logging.logger == nil {
-		return Verbose{b, nil}
-	}
-	v := logging.logger.V(int(level))
-	return Verbose{b, &logWriter{Logger: v, writeKlogBuffer: logging.loggerOptions.writeKlogBuffer}}
-}
-
-// V reports whether verbosity at the call site is at least the requested level.
-// The returned value is a struct of type Verbose, which implements Info, Infoln
-// and Infof. These methods will write to the Info log if called.
-// Thus, one may write either
-//
-//	if klog.V(2).Enabled() { klog.Info("log this") }
-//
-// or
-//
-//	klog.V(2).Info("log this")
-//
-// The second form is shorter but the first is cheaper if logging is off because it does
-// not evaluate its arguments.
-//
-// Whether an individual call to V generates a log record depends on the setting of
-// the -v and -vmodule flags; both are off by default. The V call will log if its level
-// is less than or equal to the value of the -v flag, or alternatively if its level is
-// less than or equal to the value of the -vmodule pattern matching the source file
-// containing the call.
-func V(level Level) Verbose {
-	return VDepth(1, level)
-}
-
-// VDepth is a variant of V that accepts a number of stack frames that will be
-// skipped when checking the -vmodule patterns. VDepth(0) is equivalent to
-// V().
-func VDepth(depth int, level Level) Verbose {
-	// This function tries hard to be cheap unless there's work to do.
-	// The fast path is two atomic loads and compares.
-
-	// Here is a cheap but safe test to see if V logging is enabled globally.
-	if logging.verbosity.get() >= level {
-		return newVerbose(level, true)
-	}
-
-	// It's off globally but vmodule may still be set.
-	// Here is another cheap but safe test to see if vmodule is enabled.
-	if atomic.LoadInt32(&logging.filterLength) > 0 {
-		// Now we need a proper lock to use the logging structure. The pcs field
-		// is shared so we must lock before accessing it. This is fairly expensive,
-		// but if V logging is enabled we're slow anyway.
-		logging.mu.Lock()
-		defer logging.mu.Unlock()
-		if runtime.Callers(2+depth, logging.pcs[:]) == 0 {
-			return newVerbose(level, false)
-		}
-		// runtime.Callers returns "return PCs", but we want
-		// to look up the symbolic information for the call,
-		// so subtract 1 from the PC. runtime.CallersFrames
-		// would be cleaner, but allocates.
-		pc := logging.pcs[0] - 1
-		v, ok := logging.vmap[pc]
-		if !ok {
-			v = logging.setV(pc)
-		}
-		return newVerbose(level, v >= level)
-	}
-	return newVerbose(level, false)
-}
-
-// Enabled will return true if this log level is enabled, guarded by the value
-// of v.
-// See the documentation of V for usage.
-func (v Verbose) Enabled() bool {
-	return v.enabled
-}
-
-// Info is equivalent to the global Info function, guarded by the value of v.
-// See the documentation of V for usage.
-func (v Verbose) Info(args ...interface{}) {
-	if v.enabled {
-		logging.print(severity.InfoLog, v.logger, logging.filter, args...)
-	}
-}
-
-// InfoDepth is equivalent to the global InfoDepth function, guarded by the value of v.
-// See the documentation of V for usage.
-func (v Verbose) InfoDepth(depth int, args ...interface{}) {
-	if v.enabled {
-		logging.printDepth(severity.InfoLog, v.logger, logging.filter, depth, args...)
-	}
-}
-
-// Infoln is equivalent to the global Infoln function, guarded by the value of v.
-// See the documentation of V for usage.
-func (v Verbose) Infoln(args ...interface{}) {
-	if v.enabled {
-		logging.println(severity.InfoLog, v.logger, logging.filter, args...)
-	}
-}
-
-// InfolnDepth is equivalent to the global InfolnDepth function, guarded by the value of v.
-// See the documentation of V for usage.
-func (v Verbose) InfolnDepth(depth int, args ...interface{}) {
-	if v.enabled {
-		logging.printlnDepth(severity.InfoLog, v.logger, logging.filter, depth, args...)
-	}
-}
-
-// Infof is equivalent to the global Infof function, guarded by the value of v.
-// See the documentation of V for usage.
-func (v Verbose) Infof(format string, args ...interface{}) {
-	if v.enabled {
-		logging.printf(severity.InfoLog, v.logger, logging.filter, format, args...)
-	}
-}
-
-// InfofDepth is equivalent to the global InfofDepth function, guarded by the value of v.
-// See the documentation of V for usage.
-func (v Verbose) InfofDepth(depth int, format string, args ...interface{}) {
-	if v.enabled {
-		logging.printfDepth(severity.InfoLog, v.logger, logging.filter, depth, format, args...)
-	}
-}
-
-// InfoS is equivalent to the global InfoS function, guarded by the value of v.
-// See the documentation of V for usage.
-func (v Verbose) InfoS(msg string, keysAndValues ...interface{}) {
-	if v.enabled {
-		logging.infoS(v.logger, logging.filter, 0, msg, keysAndValues...)
-	}
-}
-
-// InfoSDepth acts as InfoS but uses depth to determine which call frame to log.
-// InfoSDepth(0, "msg") is the same as InfoS("msg").
-func InfoSDepth(depth int, msg string, keysAndValues ...interface{}) {
-	logging.infoS(logging.logger, logging.filter, depth, msg, keysAndValues...)
-}
-
-// InfoSDepth is equivalent to the global InfoSDepth function, guarded by the value of v.
-// See the documentation of V for usage.
-func (v Verbose) InfoSDepth(depth int, msg string, keysAndValues ...interface{}) {
-	if v.enabled {
-		logging.infoS(v.logger, logging.filter, depth, msg, keysAndValues...)
-	}
-}
-
-// Deprecated: Use ErrorS instead.
-func (v Verbose) Error(err error, msg string, args ...interface{}) {
-	if v.enabled {
-		logging.errorS(err, v.logger, logging.filter, 0, msg, args...)
-	}
-}
-
-// ErrorS is equivalent to the global Error function, guarded by the value of v.
-// See the documentation of V for usage.
-func (v Verbose) ErrorS(err error, msg string, keysAndValues ...interface{}) {
-	if v.enabled {
-		logging.errorS(err, v.logger, logging.filter, 0, msg, keysAndValues...)
-	}
-}
-
-// Info logs to the INFO log.
-// Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
-func Info(args ...interface{}) {
-	logging.print(severity.InfoLog, logging.logger, logging.filter, args...)
-}
-
-// InfoDepth acts as Info but uses depth to determine which call frame to log.
-// InfoDepth(0, "msg") is the same as Info("msg").
-func InfoDepth(depth int, args ...interface{}) {
-	logging.printDepth(severity.InfoLog, logging.logger, logging.filter, depth, args...)
-}
-
-// Infoln logs to the INFO log.
-// Arguments are handled in the manner of fmt.Println; a newline is always appended.
-func Infoln(args ...interface{}) {
-	logging.println(severity.InfoLog, logging.logger, logging.filter, args...)
-}
-
-// InfolnDepth acts as Infoln but uses depth to determine which call frame to log.
-// InfolnDepth(0, "msg") is the same as Infoln("msg").
-func InfolnDepth(depth int, args ...interface{}) {
-	logging.printlnDepth(severity.InfoLog, logging.logger, logging.filter, depth, args...)
-}
-
-// Infof logs to the INFO log.
-// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
-func Infof(format string, args ...interface{}) {
-	logging.printf(severity.InfoLog, logging.logger, logging.filter, format, args...)
-}
-
-// InfofDepth acts as Infof but uses depth to determine which call frame to log.
-// InfofDepth(0, "msg", args...) is the same as Infof("msg", args...).
-func InfofDepth(depth int, format string, args ...interface{}) {
-	logging.printfDepth(severity.InfoLog, logging.logger, logging.filter, depth, format, args...)
-}
-
-// InfoS structured logs to the INFO log.
-// The msg argument used to add constant description to the log line.
-// The key/value pairs would be join by "=" ; a newline is always appended.
-//
-// Basic examples:
-// >> klog.InfoS("Pod status updated", "pod", "kubedns", "status", "ready")
-// output:
-// >> I1025 00:15:15.525108       1 controller_utils.go:116] "Pod status updated" pod="kubedns" status="ready"
-func InfoS(msg string, keysAndValues ...interface{}) {
-	logging.infoS(logging.logger, logging.filter, 0, msg, keysAndValues...)
-}
-
-// Warning logs to the WARNING and INFO logs.
-// Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
-func Warning(args ...interface{}) {
-	logging.print(severity.WarningLog, logging.logger, logging.filter, args...)
-}
-
-// WarningDepth acts as Warning but uses depth to determine which call frame to log.
-// WarningDepth(0, "msg") is the same as Warning("msg").
-func WarningDepth(depth int, args ...interface{}) {
-	logging.printDepth(severity.WarningLog, logging.logger, logging.filter, depth, args...)
-}
-
-// Warningln logs to the WARNING and INFO logs.
-// Arguments are handled in the manner of fmt.Println; a newline is always appended.
-func Warningln(args ...interface{}) {
-	logging.println(severity.WarningLog, logging.logger, logging.filter, args...)
-}
-
-// WarninglnDepth acts as Warningln but uses depth to determine which call frame to log.
-// WarninglnDepth(0, "msg") is the same as Warningln("msg").
-func WarninglnDepth(depth int, args ...interface{}) {
-	logging.printlnDepth(severity.WarningLog, logging.logger, logging.filter, depth, args...)
-}
-
-// Warningf logs to the WARNING and INFO logs.
-// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
-func Warningf(format string, args ...interface{}) {
-	logging.printf(severity.WarningLog, logging.logger, logging.filter, format, args...)
-}
-
-// WarningfDepth acts as Warningf but uses depth to determine which call frame to log.
-// WarningfDepth(0, "msg", args...) is the same as Warningf("msg", args...).
-func WarningfDepth(depth int, format string, args ...interface{}) {
-	logging.printfDepth(severity.WarningLog, logging.logger, logging.filter, depth, format, args...)
-}
-
-// Error logs to the ERROR, WARNING, and INFO logs.
-// Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
-func Error(args ...interface{}) {
-	logging.print(severity.ErrorLog, logging.logger, logging.filter, args...)
-}
-
-// ErrorDepth acts as Error but uses depth to determine which call frame to log.
-// ErrorDepth(0, "msg") is the same as Error("msg").
-func ErrorDepth(depth int, args ...interface{}) {
-	logging.printDepth(severity.ErrorLog, logging.logger, logging.filter, depth, args...)
-}
-
-// Errorln logs to the ERROR, WARNING, and INFO logs.
-// Arguments are handled in the manner of fmt.Println; a newline is always appended.
-func Errorln(args ...interface{}) {
-	logging.println(severity.ErrorLog, logging.logger, logging.filter, args...)
-}
-
-// ErrorlnDepth acts as Errorln but uses depth to determine which call frame to log.
-// ErrorlnDepth(0, "msg") is the same as Errorln("msg").
-func ErrorlnDepth(depth int, args ...interface{}) {
-	logging.printlnDepth(severity.ErrorLog, logging.logger, logging.filter, depth, args...)
-}
-
-// Errorf logs to the ERROR, WARNING, and INFO logs.
-// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
-func Errorf(format string, args ...interface{}) {
-	logging.printf(severity.ErrorLog, logging.logger, logging.filter, format, args...)
-}
-
-// ErrorfDepth acts as Errorf but uses depth to determine which call frame to log.
-// ErrorfDepth(0, "msg", args...) is the same as Errorf("msg", args...).
-func ErrorfDepth(depth int, format string, args ...interface{}) {
-	logging.printfDepth(severity.ErrorLog, logging.logger, logging.filter, depth, format, args...)
-}
-
-// ErrorS structured logs to the ERROR, WARNING, and INFO logs.
-// the err argument used as "err" field of log line.
-// The msg argument used to add constant description to the log line.
-// The key/value pairs would be join by "=" ; a newline is always appended.
-//
-// Basic examples:
-// >> klog.ErrorS(err, "Failed to update pod status")
-// output:
-// >> E1025 00:15:15.525108       1 controller_utils.go:114] "Failed to update pod status" err="timeout"
-func ErrorS(err error, msg string, keysAndValues ...interface{}) {
-	logging.errorS(err, logging.logger, logging.filter, 0, msg, keysAndValues...)
-}
-
-// ErrorSDepth acts as ErrorS but uses depth to determine which call frame to log.
-// ErrorSDepth(0, "msg") is the same as ErrorS("msg").
-func ErrorSDepth(depth int, err error, msg string, keysAndValues ...interface{}) {
-	logging.errorS(err, logging.logger, logging.filter, depth, msg, keysAndValues...)
-}
-
-// Fatal logs to the FATAL, ERROR, WARNING, and INFO logs,
-// prints stack trace(s), then calls OsExit(255).
-//
-// Stderr only receives a dump of the current goroutine's stack trace. Log files,
-// if there are any, receive a dump of the stack traces in all goroutines.
-//
-// Callers who want more control over handling of fatal events may instead use a
-// combination of different functions:
-//   - some info or error logging function, optionally with a stack trace
-//     value generated by github.com/go-logr/lib/dbg.Backtrace
-//   - Flush to flush pending log data
-//   - panic, os.Exit or returning to the caller with an error
-//
-// Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
-func Fatal(args ...interface{}) {
-	logging.print(severity.FatalLog, logging.logger, logging.filter, args...)
-}
-
-// FatalDepth acts as Fatal but uses depth to determine which call frame to log.
-// FatalDepth(0, "msg") is the same as Fatal("msg").
-func FatalDepth(depth int, args ...interface{}) {
-	logging.printDepth(severity.FatalLog, logging.logger, logging.filter, depth, args...)
-}
-
-// Fatalln logs to the FATAL, ERROR, WARNING, and INFO logs,
-// including a stack trace of all running goroutines, then calls OsExit(255).
-// Arguments are handled in the manner of fmt.Println; a newline is always appended.
-func Fatalln(args ...interface{}) {
-	logging.println(severity.FatalLog, logging.logger, logging.filter, args...)
-}
-
-// FatallnDepth acts as Fatalln but uses depth to determine which call frame to log.
-// FatallnDepth(0, "msg") is the same as Fatalln("msg").
-func FatallnDepth(depth int, args ...interface{}) {
-	logging.printlnDepth(severity.FatalLog, logging.logger, logging.filter, depth, args...)
-}
-
-// Fatalf logs to the FATAL, ERROR, WARNING, and INFO logs,
-// including a stack trace of all running goroutines, then calls OsExit(255).
-// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
-func Fatalf(format string, args ...interface{}) {
-	logging.printf(severity.FatalLog, logging.logger, logging.filter, format, args...)
-}
-
-// FatalfDepth acts as Fatalf but uses depth to determine which call frame to log.
-// FatalfDepth(0, "msg", args...) is the same as Fatalf("msg", args...).
-func FatalfDepth(depth int, format string, args ...interface{}) {
-	logging.printfDepth(severity.FatalLog, logging.logger, logging.filter, depth, format, args...)
-}
-
-// fatalNoStacks is non-zero if we are to exit without dumping goroutine stacks.
-// It allows Exit and relatives to use the Fatal logs.
-var fatalNoStacks uint32
-
-// Exit logs to the FATAL, ERROR, WARNING, and INFO logs, then calls OsExit(1).
-// Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
-func Exit(args ...interface{}) {
-	atomic.StoreUint32(&fatalNoStacks, 1)
-	logging.print(severity.FatalLog, logging.logger, logging.filter, args...)
-}
-
-// ExitDepth acts as Exit but uses depth to determine which call frame to log.
-// ExitDepth(0, "msg") is the same as Exit("msg").
-func ExitDepth(depth int, args ...interface{}) {
-	atomic.StoreUint32(&fatalNoStacks, 1)
-	logging.printDepth(severity.FatalLog, logging.logger, logging.filter, depth, args...)
-}
-
-// Exitln logs to the FATAL, ERROR, WARNING, and INFO logs, then calls OsExit(1).
-func Exitln(args ...interface{}) {
-	atomic.StoreUint32(&fatalNoStacks, 1)
-	logging.println(severity.FatalLog, logging.logger, logging.filter, args...)
-}
-
-// ExitlnDepth acts as Exitln but uses depth to determine which call frame to log.
-// ExitlnDepth(0, "msg") is the same as Exitln("msg").
-func ExitlnDepth(depth int, args ...interface{}) {
-	atomic.StoreUint32(&fatalNoStacks, 1)
-	logging.printlnDepth(severity.FatalLog, logging.logger, logging.filter, depth, args...)
-}
-
-// Exitf logs to the FATAL, ERROR, WARNING, and INFO logs, then calls OsExit(1).
-// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
-func Exitf(format string, args ...interface{}) {
-	atomic.StoreUint32(&fatalNoStacks, 1)
-	logging.printf(severity.FatalLog, logging.logger, logging.filter, format, args...)
-}
-
-// ExitfDepth acts as Exitf but uses depth to determine which call frame to log.
-// ExitfDepth(0, "msg", args...) is the same as Exitf("msg", args...).
-func ExitfDepth(depth int, format string, args ...interface{}) {
-	atomic.StoreUint32(&fatalNoStacks, 1)
-	logging.printfDepth(severity.FatalLog, logging.logger, logging.filter, depth, format, args...)
-}
-
-// LogFilter is a collection of functions that can filter all logging calls,
-// e.g. for sanitization of arguments and prevent accidental leaking of secrets.
-type LogFilter interface {
-	Filter(args []interface{}) []interface{}
-	FilterF(format string, args []interface{}) (string, []interface{})
-	FilterS(msg string, keysAndValues []interface{}) (string, []interface{})
-}
-
-// SetLogFilter installs a filter that is used for all log calls.
-//
-// Modifying the filter is not thread-safe and should be done while no other
-// goroutines invoke log calls, usually during program initialization.
-func SetLogFilter(filter LogFilter) {
-	logging.filter = filter
-}
diff --git a/vendor/k8s.io/klog/v2/klog_file.go b/vendor/k8s.io/klog/v2/klog_file.go
deleted file mode 100644
index 8bee16204..000000000
--- a/vendor/k8s.io/klog/v2/klog_file.go
+++ /dev/null
@@ -1,130 +0,0 @@
-// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/
-//
-// Copyright 2013 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// File I/O for logs.
-
-package klog
-
-import (
-	"errors"
-	"fmt"
-	"os"
-	"path/filepath"
-	"strings"
-	"sync"
-	"time"
-)
-
-// MaxSize is the maximum size of a log file in bytes.
-var MaxSize uint64 = 1024 * 1024 * 1800
-
-// logDirs lists the candidate directories for new log files.
-var logDirs []string
-
-func createLogDirs() {
-	if logging.logDir != "" {
-		logDirs = append(logDirs, logging.logDir)
-	}
-	logDirs = append(logDirs, os.TempDir())
-}
-
-var (
-	pid          = os.Getpid()
-	program      = filepath.Base(os.Args[0])
-	host         = "unknownhost"
-	userName     = "unknownuser"
-	userNameOnce sync.Once
-)
-
-func init() {
-	if h, err := os.Hostname(); err == nil {
-		host = shortHostname(h)
-	}
-}
-
-// shortHostname returns its argument, truncating at the first period.
-// For instance, given "www.google.com" it returns "www".
-func shortHostname(hostname string) string {
-	if i := strings.Index(hostname, "."); i >= 0 {
-		return hostname[:i]
-	}
-	return hostname
-}
-
-// logName returns a new log file name containing tag, with start time t, and
-// the name for the symlink for tag.
-func logName(tag string, t time.Time) (name, link string) {
-	name = fmt.Sprintf("%s.%s.%s.log.%s.%04d%02d%02d-%02d%02d%02d.%d",
-		program,
-		host,
-		getUserName(),
-		tag,
-		t.Year(),
-		t.Month(),
-		t.Day(),
-		t.Hour(),
-		t.Minute(),
-		t.Second(),
-		pid)
-	return name, program + "." + tag
-}
-
-var onceLogDirs sync.Once
-
-// create creates a new log file and returns the file and its filename, which
-// contains tag ("INFO", "FATAL", etc.) and t.  If the file is created
-// successfully, create also attempts to update the symlink for that tag, ignoring
-// errors.
-// The startup argument indicates whether this is the initial startup of klog.
-// If startup is true, existing files are opened for appending instead of truncated.
-func create(tag string, t time.Time, startup bool) (f *os.File, filename string, err error) {
-	if logging.logFile != "" {
-		f, err := openOrCreate(logging.logFile, startup)
-		if err == nil {
-			return f, logging.logFile, nil
-		}
-		return nil, "", fmt.Errorf("log: unable to create log: %v", err)
-	}
-	onceLogDirs.Do(createLogDirs)
-	if len(logDirs) == 0 {
-		return nil, "", errors.New("log: no log dirs")
-	}
-	name, link := logName(tag, t)
-	var lastErr error
-	for _, dir := range logDirs {
-		fname := filepath.Join(dir, name)
-		f, err := openOrCreate(fname, startup)
-		if err == nil {
-			symlink := filepath.Join(dir, link)
-			_ = os.Remove(symlink)        // ignore err
-			_ = os.Symlink(name, symlink) // ignore err
-			return f, fname, nil
-		}
-		lastErr = err
-	}
-	return nil, "", fmt.Errorf("log: cannot create log: %v", lastErr)
-}
-
-// The startup argument indicates whether this is the initial startup of klog.
-// If startup is true, existing files are opened for appending instead of truncated.
-func openOrCreate(name string, startup bool) (*os.File, error) {
-	if startup {
-		f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
-		return f, err
-	}
-	f, err := os.Create(name)
-	return f, err
-}
diff --git a/vendor/k8s.io/klog/v2/klog_file_others.go b/vendor/k8s.io/klog/v2/klog_file_others.go
deleted file mode 100644
index aa4672685..000000000
--- a/vendor/k8s.io/klog/v2/klog_file_others.go
+++ /dev/null
@@ -1,19 +0,0 @@
-//go:build !windows
-// +build !windows
-
-package klog
-
-import (
-	"os/user"
-)
-
-func getUserName() string {
-	userNameOnce.Do(func() {
-		current, err := user.Current()
-		if err == nil {
-			userName = current.Username
-		}
-	})
-
-	return userName
-}
diff --git a/vendor/k8s.io/klog/v2/klog_file_windows.go b/vendor/k8s.io/klog/v2/klog_file_windows.go
deleted file mode 100644
index 2517f9c53..000000000
--- a/vendor/k8s.io/klog/v2/klog_file_windows.go
+++ /dev/null
@@ -1,34 +0,0 @@
-//go:build windows
-// +build windows
-
-package klog
-
-import (
-	"os"
-	"strings"
-)
-
-func getUserName() string {
-	userNameOnce.Do(func() {
-		// On Windows, the Go 'user' package requires netapi32.dll.
-		// This affects Windows Nano Server:
-		//   https://github.com/golang/go/issues/21867
-		// Fallback to using environment variables.
-		u := os.Getenv("USERNAME")
-		if len(u) == 0 {
-			return
-		}
-		// Sanitize the USERNAME since it may contain filepath separators.
-		u = strings.Replace(u, `\`, "_", -1)
-
-		// user.Current().Username normally produces something like 'USERDOMAIN\USERNAME'
-		d := os.Getenv("USERDOMAIN")
-		if len(d) != 0 {
-			userName = d + "_" + u
-		} else {
-			userName = u
-		}
-	})
-
-	return userName
-}
diff --git a/vendor/k8s.io/klog/v2/klogr.go b/vendor/k8s.io/klog/v2/klogr.go
deleted file mode 100644
index efec96fd4..000000000
--- a/vendor/k8s.io/klog/v2/klogr.go
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
-Copyright 2021 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package klog
-
-import (
-	"github.com/go-logr/logr"
-
-	"k8s.io/klog/v2/internal/serialize"
-)
-
-const (
-	// nameKey is used to log the `WithName` values as an additional attribute.
-	nameKey = "logger"
-)
-
-// NewKlogr returns a logger that is functionally identical to
-// klogr.NewWithOptions(klogr.FormatKlog), i.e. it passes through to klog. The
-// difference is that it uses a simpler implementation.
-func NewKlogr() Logger {
-	return New(&klogger{})
-}
-
-// klogger is a subset of klogr/klogr.go. It had to be copied to break an
-// import cycle (klogr wants to use klog, and klog wants to use klogr).
-type klogger struct {
-	callDepth int
-
-	// hasPrefix is true if the first entry in values is the special
-	// nameKey key/value. Such an entry gets added and later updated in
-	// WithName.
-	hasPrefix bool
-
-	values []interface{}
-	groups string
-}
-
-func (l *klogger) Init(info logr.RuntimeInfo) {
-	l.callDepth += info.CallDepth
-}
-
-func (l *klogger) Info(level int, msg string, kvList ...interface{}) {
-	merged := serialize.MergeKVs(l.values, kvList)
-	// Skip this function.
-	VDepth(l.callDepth+1, Level(level)).InfoSDepth(l.callDepth+1, msg, merged...)
-}
-
-func (l *klogger) Enabled(level int) bool {
-	return VDepth(l.callDepth+1, Level(level)).Enabled()
-}
-
-func (l *klogger) Error(err error, msg string, kvList ...interface{}) {
-	merged := serialize.MergeKVs(l.values, kvList)
-	ErrorSDepth(l.callDepth+1, err, msg, merged...)
-}
-
-// WithName returns a new logr.Logger with the specified name appended.  klogr
-// uses '.' characters to separate name elements.  Callers should not pass '.'
-// in the provided name string, but this library does not actually enforce that.
-func (l klogger) WithName(name string) logr.LogSink {
-	if l.hasPrefix {
-		// Copy slice and modify value. No length checks and type
-		// assertions are needed because hasPrefix is only true if the
-		// first two elements exist and are key/value strings.
-		v := make([]interface{}, 0, len(l.values))
-		v = append(v, l.values...)
-		prefix, _ := v[1].(string)
-		v[1] = prefix + "." + name
-		l.values = v
-	} else {
-		// Preprend new key/value pair.
-		v := make([]interface{}, 0, 2+len(l.values))
-		v = append(v, nameKey, name)
-		v = append(v, l.values...)
-		l.values = v
-		l.hasPrefix = true
-	}
-	return &l
-}
-
-func (l klogger) WithValues(kvList ...interface{}) logr.LogSink {
-	l.values = serialize.WithValues(l.values, kvList)
-	return &l
-}
-
-func (l klogger) WithCallDepth(depth int) logr.LogSink {
-	l.callDepth += depth
-	return &l
-}
-
-var _ logr.LogSink = &klogger{}
-var _ logr.CallDepthLogSink = &klogger{}
diff --git a/vendor/k8s.io/klog/v2/klogr_slog.go b/vendor/k8s.io/klog/v2/klogr_slog.go
deleted file mode 100644
index c77d7baaf..000000000
--- a/vendor/k8s.io/klog/v2/klogr_slog.go
+++ /dev/null
@@ -1,96 +0,0 @@
-//go:build go1.21
-// +build go1.21
-
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package klog
-
-import (
-	"context"
-	"log/slog"
-	"strconv"
-	"time"
-
-	"github.com/go-logr/logr"
-
-	"k8s.io/klog/v2/internal/buffer"
-	"k8s.io/klog/v2/internal/serialize"
-	"k8s.io/klog/v2/internal/severity"
-	"k8s.io/klog/v2/internal/sloghandler"
-)
-
-func (l *klogger) Handle(ctx context.Context, record slog.Record) error {
-	if logging.logger != nil {
-		if slogSink, ok := logging.logger.GetSink().(logr.SlogSink); ok {
-			// Let that logger do the work.
-			return slogSink.Handle(ctx, record)
-		}
-	}
-
-	return sloghandler.Handle(ctx, record, l.groups, slogOutput)
-}
-
-// slogOutput corresponds to several different functions in klog.go.
-// It goes through some of the same checks and formatting steps before
-// it ultimately converges by calling logging.printWithInfos.
-func slogOutput(file string, line int, now time.Time, err error, s severity.Severity, msg string, kvList []interface{}) {
-	// See infoS.
-	if logging.logger != nil {
-		// Taking this path happens when klog has a logger installed
-		// as backend which doesn't support slog. Not good, we have to
-		// guess about the call depth and drop the actual location.
-		logger := logging.logger.WithCallDepth(2)
-		if s > severity.ErrorLog {
-			logger.Error(err, msg, kvList...)
-		} else {
-			logger.Info(msg, kvList...)
-		}
-		return
-	}
-
-	// See printS.
-	b := buffer.GetBuffer()
-	b.WriteString(strconv.Quote(msg))
-	if err != nil {
-		serialize.KVListFormat(&b.Buffer, "err", err)
-	}
-	serialize.KVListFormat(&b.Buffer, kvList...)
-
-	// See print + header.
-	buf := logging.formatHeader(s, file, line, now)
-	logging.printWithInfos(buf, file, line, s, nil, nil, 0, &b.Buffer)
-
-	buffer.PutBuffer(b)
-}
-
-func (l *klogger) WithAttrs(attrs []slog.Attr) logr.SlogSink {
-	clone := *l
-	clone.values = serialize.WithValues(l.values, sloghandler.Attrs2KVList(l.groups, attrs))
-	return &clone
-}
-
-func (l *klogger) WithGroup(name string) logr.SlogSink {
-	clone := *l
-	if clone.groups != "" {
-		clone.groups += "." + name
-	} else {
-		clone.groups = name
-	}
-	return &clone
-}
-
-var _ logr.SlogSink = &klogger{}
diff --git a/vendor/k8s.io/klog/v2/safeptr.go b/vendor/k8s.io/klog/v2/safeptr.go
deleted file mode 100644
index bbe24c2e8..000000000
--- a/vendor/k8s.io/klog/v2/safeptr.go
+++ /dev/null
@@ -1,34 +0,0 @@
-//go:build go1.18
-// +build go1.18
-
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package klog
-
-// SafePtr is a function that takes a pointer of any type (T) as an argument.
-// If the provided pointer is not nil, it returns the same pointer. If it is nil, it returns nil instead.
-//
-// This function is particularly useful to prevent nil pointer dereferencing when:
-//
-//   - The type implements interfaces that are called by the logger, such as `fmt.Stringer`.
-//   - And these interface implementations do not perform nil checks themselves.
-func SafePtr[T any](p *T) any {
-	if p == nil {
-		return nil
-	}
-	return p
-}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 36e0d4dc7..7eb3ce7bf 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -175,7 +175,7 @@ github.com/go-sql-driver/mysql
 # github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
 ## explicit
 github.com/golang/groupcache/lru
-# github.com/google/certificate-transparency-go v1.3.2-0.20250506133818-bc7acd89f703
+# github.com/google/certificate-transparency-go v1.3.2-0.20250507091337-0eddb39e94f8
 ## explicit; go 1.23.0
 github.com/google/certificate-transparency-go
 github.com/google/certificate-transparency-go/asn1
@@ -563,12 +563,3 @@ gopkg.in/tomb.v1
 # gopkg.in/yaml.v3 v3.0.1
 ## explicit
 gopkg.in/yaml.v3
-# k8s.io/klog/v2 v2.130.1
-## explicit; go 1.18
-k8s.io/klog/v2
-k8s.io/klog/v2/internal/buffer
-k8s.io/klog/v2/internal/clock
-k8s.io/klog/v2/internal/dbg
-k8s.io/klog/v2/internal/serialize
-k8s.io/klog/v2/internal/severity
-k8s.io/klog/v2/internal/sloghandler



Test patch:
diff --git a/ca/ca_test.go b/ca/ca_test.go
index df549663b..c6614848c 100644
--- a/ca/ca_test.go
+++ b/ca/ca_test.go
@@ -147,7 +147,7 @@ func setup(t *testing.T) *testCtx {
 	fc := clock.NewFake()
 	fc.Add(1 * time.Hour)
 
-	pa, err := policy.New(nil, blog.NewMock())
+	pa, err := policy.New(nil, nil, blog.NewMock())
 	test.AssertNotError(t, err, "Couldn't create PA")
 	err = pa.LoadHostnamePolicyFile("../test/hostname-policy.yaml")
 	test.AssertNotError(t, err, "Couldn't set hostname policy")
diff --git a/cmd/cert-checker/main_test.go b/cmd/cert-checker/main_test.go
index d8f9e8aaa..4a79d1b80 100644
--- a/cmd/cert-checker/main_test.go
+++ b/cmd/cert-checker/main_test.go
@@ -31,6 +31,7 @@ import (
 	"github.com/letsencrypt/boulder/ctpolicy/loglist"
 	"github.com/letsencrypt/boulder/goodkey"
 	"github.com/letsencrypt/boulder/goodkey/sagoodkey"
+	"github.com/letsencrypt/boulder/identifier"
 	"github.com/letsencrypt/boulder/linter"
 	blog "github.com/letsencrypt/boulder/log"
 	"github.com/letsencrypt/boulder/metrics"
@@ -52,7 +53,10 @@ var (
 
 func init() {
 	var err error
-	pa, err = policy.New(map[core.AcmeChallenge]bool{}, blog.NewMock())
+	pa, err = policy.New(
+		map[identifier.IdentifierType]bool{identifier.TypeDNS: true, identifier.TypeIP: true},
+		map[core.AcmeChallenge]bool{},
+		blog.NewMock())
 	if err != nil {
 		log.Fatal(err)
 	}
diff --git a/cmd/expiration-mailer/main_test.go b/cmd/expiration-mailer/main_test.go
index 4d96ce8e2..860bd7196 100644
--- a/cmd/expiration-mailer/main_test.go
+++ b/cmd/expiration-mailer/main_test.go
@@ -516,15 +516,18 @@ func insertCertificate(cert certDERWithRegID, lastNagSent time.Time) error {
 		return fmt.Errorf("inserting certificate: %w", err)
 	}
 
-	return setupDBMap.Insert(ctx, &core.CertificateStatus{
-		Serial:                core.SerialToString(parsedCert.SerialNumber),
-		LastExpirationNagSent: lastNagSent,
-		Status:                core.OCSPStatusGood,
-		NotAfter:              parsedCert.NotAfter,
-		OCSPLastUpdated:       time.Time{},
-		RevokedDate:           time.Time{},
-		RevokedReason:         0,
-	})
+	_, err = setupDBMap.ExecContext(context.Background(),
+		`INSERT INTO certificateStatus
+		(serial, notAfter, status, ocspLastUpdated, revokedDate, revokedReason, lastExpirationNagSent)
+		VALUES (?, ?, ?, ?, ?, ?, ?)`,
+		core.SerialToString(parsedCert.SerialNumber),
+		parsedCert.NotAfter,
+		core.OCSPStatusGood,
+		time.Time{},
+		time.Time{},
+		0,
+		lastNagSent)
+	return err
 }
 
 func addExpiringCerts(t *testing.T, ctx *testCtx) []certDERWithRegID {
diff --git a/cmd/rocsp-tool/client_test.go b/cmd/rocsp-tool/client_test.go
index ddb11f015..31f483d9b 100644
--- a/cmd/rocsp-tool/client_test.go
+++ b/cmd/rocsp-tool/client_test.go
@@ -15,6 +15,7 @@ import (
 	capb "github.com/letsencrypt/boulder/ca/proto"
 	"github.com/letsencrypt/boulder/cmd"
 	"github.com/letsencrypt/boulder/core"
+	"github.com/letsencrypt/boulder/db"
 	blog "github.com/letsencrypt/boulder/log"
 	"github.com/letsencrypt/boulder/metrics"
 	"github.com/letsencrypt/boulder/rocsp"
@@ -50,29 +51,34 @@ func makeClient() (*rocsp.RWClient, clock.Clock) {
 	return rocsp.NewWritingClient(rdb, 500*time.Millisecond, clk, metrics.NoopRegisterer), clk
 }
 
-func TestGetStartingID(t *testing.T) {
-	ctx := context.Background()
+func insertCertificateStatus(t *testing.T, dbMap db.Executor, serial string, notAfter, ocspLastUpdated time.Time) int64 {
+	result, err := dbMap.ExecContext(context.Background(),
+		`INSERT INTO certificateStatus
+		(serial, notAfter, status, ocspLastUpdated, revokedDate, revokedReason, lastExpirationNagSent, issuerID)
+		VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
+		serial,
+		notAfter,
+		core.OCSPStatusGood,
+		ocspLastUpdated,
+		time.Time{},
+		0,
+		time.Time{},
+		99)
+	test.AssertNotError(t, err, "inserting certificate status")
+	id, err := result.LastInsertId()
+	test.AssertNotError(t, err, "getting last insert ID")
+	return id
+}
 
+func TestGetStartingID(t *testing.T) {
 	clk := clock.NewFake()
 	dbMap, err := sa.DBMapForTest(vars.DBConnSAFullPerms)
 	test.AssertNotError(t, err, "failed setting up db client")
 	defer test.ResetBoulderTestDatabase(t)()
 
-	cs := core.CertificateStatus{
-		Serial:   "1337",
-		NotAfter: clk.Now().Add(12 * time.Hour),
-	}
-	err = dbMap.Insert(ctx, &cs)
-	test.AssertNotError(t, err, "inserting certificate status")
-	firstID := cs.ID
+	firstID := insertCertificateStatus(t, dbMap, "1337", clk.Now().Add(12*time.Hour), time.Time{})
+	secondID := insertCertificateStatus(t, dbMap, "1338", clk.Now().Add(36*time.Hour), time.Time{})
 
-	cs = core.CertificateStatus{
-		Serial:   "1338",
-		NotAfter: clk.Now().Add(36 * time.Hour),
-	}
-	err = dbMap.Insert(ctx, &cs)
-	test.AssertNotError(t, err, "inserting certificate status")
-	secondID := cs.ID
 	t.Logf("first ID %d, second ID %d", firstID, secondID)
 
 	clk.Sleep(48 * time.Hour)
@@ -131,11 +137,7 @@ func TestLoadFromDB(t *testing.T) {
 	defer test.ResetBoulderTestDatabase(t)
 
 	for i := range 100 {
-		err = dbMap.Insert(context.Background(), &core.CertificateStatus{
-			Serial:          fmt.Sprintf("%036x", i),
-			NotAfter:        clk.Now().Add(200 * time.Hour),
-			OCSPLastUpdated: clk.Now(),
-		})
+		insertCertificateStatus(t, dbMap, fmt.Sprintf("%036x", i), clk.Now().Add(200*time.Hour), clk.Now())
 		if err != nil {
 			t.Fatalf("Failed to insert certificateStatus: %s", err)
 		}
diff --git a/cmd/shell_test.go b/cmd/shell_test.go
index baaaaba0d..073eaf5fa 100644
--- a/cmd/shell_test.go
+++ b/cmd/shell_test.go
@@ -23,22 +23,24 @@ var (
 	validPAConfig = []byte(`{
   "dbConnect": "dummyDBConnect",
   "enforcePolicyWhitelist": false,
-  "challenges": { "http-01": true }
+  "challenges": { "http-01": true },
+  "identifiers": { "dns": true, "ip": true }
 }`)
 	invalidPAConfig = []byte(`{
   "dbConnect": "dummyDBConnect",
   "enforcePolicyWhitelist": false,
-  "challenges": { "nonsense": true }
+  "challenges": { "nonsense": true },
+  "identifiers": { "openpgp": true }
 }`)
-	noChallengesPAConfig = []byte(`{
+	noChallengesIdentsPAConfig = []byte(`{
   "dbConnect": "dummyDBConnect",
   "enforcePolicyWhitelist": false
 }`)
-
-	emptyChallengesPAConfig = []byte(`{
+	emptyChallengesIdentsPAConfig = []byte(`{
   "dbConnect": "dummyDBConnect",
   "enforcePolicyWhitelist": false,
-  "challenges": {}
+  "challenges": {},
+  "identifiers": {}
 }`)
 )
 
@@ -47,21 +49,25 @@ func TestPAConfigUnmarshal(t *testing.T) {
 	err := json.Unmarshal(validPAConfig, &pc1)
 	test.AssertNotError(t, err, "Failed to unmarshal PAConfig")
 	test.AssertNotError(t, pc1.CheckChallenges(), "Flagged valid challenges as bad")
+	test.AssertNotError(t, pc1.CheckIdentifiers(), "Flagged valid identifiers as bad")
 
 	var pc2 PAConfig
 	err = json.Unmarshal(invalidPAConfig, &pc2)
 	test.AssertNotError(t, err, "Failed to unmarshal PAConfig")
 	test.AssertError(t, pc2.CheckChallenges(), "Considered invalid challenges as good")
+	test.AssertError(t, pc2.CheckIdentifiers(), "Considered invalid identifiers as good")
 
 	var pc3 PAConfig
-	err = json.Unmarshal(noChallengesPAConfig, &pc3)
+	err = json.Unmarshal(noChallengesIdentsPAConfig, &pc3)
 	test.AssertNotError(t, err, "Failed to unmarshal PAConfig")
 	test.AssertError(t, pc3.CheckChallenges(), "Disallow empty challenges map")
+	test.AssertNotError(t, pc3.CheckIdentifiers(), "Disallowed empty identifiers map")
 
 	var pc4 PAConfig
-	err = json.Unmarshal(emptyChallengesPAConfig, &pc4)
+	err = json.Unmarshal(emptyChallengesIdentsPAConfig, &pc4)
 	test.AssertNotError(t, err, "Failed to unmarshal PAConfig")
 	test.AssertError(t, pc4.CheckChallenges(), "Disallow empty challenges map")
+	test.AssertNotError(t, pc4.CheckIdentifiers(), "Disallowed empty identifiers map")
 }
 
 func TestMysqlLogger(t *testing.T) {
diff --git a/ctpolicy/ctconfig/ctconfig_test.go b/ctpolicy/ctconfig/ctconfig_test.go
deleted file mode 100644
index d8d710f39..000000000
--- a/ctpolicy/ctconfig/ctconfig_test.go
+++ /dev/null
@@ -1,116 +0,0 @@
-package ctconfig
-
-import (
-	"testing"
-	"time"
-
-	"github.com/jmhodges/clock"
-	"github.com/letsencrypt/boulder/test"
-)
-
-func TestTemporalSetup(t *testing.T) {
-	for _, tc := range []struct {
-		ts  TemporalSet
-		err string
-	}{
-		{
-			ts:  TemporalSet{},
-			err: "Name cannot be empty",
-		},
-		{
-			ts: TemporalSet{
-				Name: "temporal set",
-			},
-			err: "temporal set contains no shards",
-		},
-		{
-			ts: TemporalSet{
-				Name: "temporal set",
-				Shards: []LogShard{
-					{
-						WindowStart: time.Time{},
-						WindowEnd:   time.Time{},
-					},
-				},
-			},
-			err: "WindowStart must be before WindowEnd",
-		},
-		{
-			ts: TemporalSet{
-				Name: "temporal set",
-				Shards: []LogShard{
-					{
-						WindowStart: time.Time{}.Add(time.Hour),
-						WindowEnd:   time.Time{},
-					},
-				},
-			},
-			err: "WindowStart must be before WindowEnd",
-		},
-		{
-			ts: TemporalSet{
-				Name: "temporal set",
-				Shards: []LogShard{
-					{
-						WindowStart: time.Time{},
-						WindowEnd:   time.Time{}.Add(time.Hour),
-					},
-				},
-			},
-			err: "",
-		},
-	} {
-		err := tc.ts.Setup()
-		if err != nil && tc.err != err.Error() {
-			t.Errorf("got error %q, wanted %q", err, tc.err)
-		} else if err == nil && tc.err != "" {
-			t.Errorf("unexpected error %q", err)
-		}
-	}
-}
-
-func TestLogInfo(t *testing.T) {
-	ld := LogDescription{
-		URI: "basic-uri",
-		Key: "basic-key",
-	}
-	uri, key, err := ld.Info(time.Time{})
-	test.AssertNotError(t, err, "Info failed")
-	test.AssertEquals(t, uri, ld.URI)
-	test.AssertEquals(t, key, ld.Key)
-
-	fc := clock.NewFake()
-	ld.TemporalSet = &TemporalSet{}
-	_, _, err = ld.Info(fc.Now())
-	test.AssertError(t, err, "Info should fail with a TemporalSet with no viable shards")
-	ld.TemporalSet.Shards = []LogShard{{WindowStart: fc.Now().Add(time.Hour), WindowEnd: fc.Now().Add(time.Hour * 2)}}
-	_, _, err = ld.Info(fc.Now())
-	test.AssertError(t, err, "Info should fail with a TemporalSet with no viable shards")
-
-	fc.Add(time.Hour * 4)
-	now := fc.Now()
-	ld.TemporalSet.Shards = []LogShard{
-		{
-			WindowStart: now.Add(time.Hour * -4),
-			WindowEnd:   now.Add(time.Hour * -2),
-			URI:         "a",
-			Key:         "a",
-		},
-		{
-			WindowStart: now.Add(time.Hour * -2),
-			WindowEnd:   now.Add(time.Hour * 2),
-			URI:         "b",
-			Key:         "b",
-		},
-		{
-			WindowStart: now.Add(time.Hour * 2),
-			WindowEnd:   now.Add(time.Hour * 4),
-			URI:         "c",
-			Key:         "c",
-		},
-	}
-	uri, key, err = ld.Info(now)
-	test.AssertNotError(t, err, "Info failed")
-	test.AssertEquals(t, uri, "b")
-	test.AssertEquals(t, key, "b")
-}
diff --git a/email/exporter_test.go b/email/exporter_test.go
index cccf6af0d..993195899 100644
--- a/email/exporter_test.go
+++ b/email/exporter_test.go
@@ -12,6 +12,8 @@ import (
 	blog "github.com/letsencrypt/boulder/log"
 	"github.com/letsencrypt/boulder/metrics"
 	"github.com/letsencrypt/boulder/test"
+
+	"github.com/prometheus/client_golang/prometheus"
 )
 
 var ctx = context.Background()
@@ -88,6 +90,9 @@ func TestSendContacts(t *testing.T) {
 	}
 	test.AssertSliceContains(t, gotContacts, wantContacts[0])
 	test.AssertSliceContains(t, gotContacts, wantContacts[1])
+
+	// Check that the error counter was not incremented.
+	test.AssertMetricWithLabelsEquals(t, exporter.pardotErrorCounter, prometheus.Labels{}, 0)
 }
 
 func TestSendContactsQueueFull(t *testing.T) {
@@ -130,3 +135,31 @@ func TestSendContactsQueueDrains(t *testing.T) {
 
 	test.AssertEquals(t, 100, len(clientImpl.getCreatedContacts()))
 }
+
+type mockAlwaysFailClient struct{}
+
+func (m *mockAlwaysFailClient) SendContact(email string) error {
+	return fmt.Errorf("simulated failure")
+}
+
+func TestSendContactsErrorMetrics(t *testing.T) {
+	t.Parallel()
+
+	mockClient := &mockAlwaysFailClient{}
+	exporter := NewExporterImpl(mockClient, 1000000, 5, metrics.NoopRegisterer, blog.NewMock())
+
+	daemonCtx, cancel := context.WithCancel(context.Background())
+	exporter.Start(daemonCtx)
+
+	_, err := exporter.SendContacts(ctx, &emailpb.SendContactsRequest{
+		Emails: []string{"test@example.com"},
+	})
+	test.AssertNotError(t, err, "Error creating contacts")
+
+	// Drain the queue.
+	cancel()
+	exporter.Drain()
+
+	// Check that the error counter was incremented.
+	test.AssertMetricWithLabelsEquals(t, exporter.pardotErrorCounter, prometheus.Labels{}, 1)
+}
diff --git a/policy/pa_test.go b/policy/pa_test.go
index d7c586b4f..ac1d07ac0 100644
--- a/policy/pa_test.go
+++ b/policy/pa_test.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"net/netip"
 	"os"
+	"strings"
 	"testing"
 
 	"gopkg.in/yaml.v3"
@@ -23,7 +24,12 @@ func paImpl(t *testing.T) *AuthorityImpl {
 		core.ChallengeTypeTLSALPN01: true,
 	}
 
-	pa, err := New(enabledChallenges, blog.NewMock())
+	enabledIdentifiers := map[identifier.IdentifierType]bool{
+		identifier.TypeDNS: true,
+		identifier.TypeIP:  true,
+	}
+
+	pa, err := New(enabledIdentifiers, enabledChallenges, blog.NewMock())
 	if err != nil {
 		t.Fatalf("Couldn't create policy implementation: %s", err)
 	}
@@ -32,110 +38,146 @@ func paImpl(t *testing.T) *AuthorityImpl {
 
 func TestWellFormedIdentifiers(t *testing.T) {
 	testCases := []struct {
-		domain string
-		err    error
+		ident identifier.ACMEIdentifier
+		err   error
 	}{
-		{``, errEmptyName},                    // Empty name
-		{`zomb!.com`, errInvalidDNSCharacter}, // ASCII character out of range
-		{`emailaddress@myseriously.present.com`, errInvalidDNSCharacter},
-		{`user:pass@myseriously.present.com`, errInvalidDNSCharacter},
-		{`zömbo.com`, errInvalidDNSCharacter},                              // non-ASCII character
-		{`127.0.0.1`, errIPAddress},                                        // IPv4 address
-		{`fe80::1:1`, errInvalidDNSCharacter},                              // IPv6 addresses
-		{`[2001:db8:85a3:8d3:1319:8a2e:370:7348]`, errInvalidDNSCharacter}, // unexpected IPv6 variants
-		{`[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443`, errInvalidDNSCharacter},
-		{`2001:db8::/32`, errInvalidDNSCharacter},
-		{`a.b.c.d.e.f.g.h.i.j.k`, errTooManyLabels}, // Too many labels (>10)
-
-		{`www.0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345.com`, errNameTooLong}, // Too long (254 characters)
-
-		{`www.ef0123456789abcdef013456789abcdef012345.789abcdef012345679abcdef0123456789abcdef01234.6789abcdef0123456789abcdef0.23456789abcdef0123456789a.cdef0123456789abcdef0123456789ab.def0123456789abcdef0123456789.bcdef0123456789abcdef012345.com`, nil}, // OK, not too long (240 characters)
-
-		{`www.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz.com`, errLabelTooLong}, // Label too long (>63 characters)
-
-		{`www.-ombo.com`, errInvalidDNSCharacter}, // Label starts with '-'
-		{`www.zomb-.com`, errInvalidDNSCharacter}, // Label ends with '-'
-		{`xn--.net`, errInvalidDNSCharacter},      // Label ends with '-'
-		{`-0b.net`, errInvalidDNSCharacter},       // First label begins with '-'
-		{`-0.net`, errInvalidDNSCharacter},        // First label begins with '-'
-		{`-.net`, errInvalidDNSCharacter},         // First label is only '-'
-		{`---.net`, errInvalidDNSCharacter},       // First label is only hyphens
-		{`0`, errTooFewLabels},
-		{`1`, errTooFewLabels},
-		{`*`, errMalformedWildcard},
-		{`**`, errTooManyWildcards},
-		{`*.*`, errTooManyWildcards},
-		{`zombo*com`, errMalformedWildcard},
-		{`*.com`, errICANNTLDWildcard},
-		{`..a`, errLabelTooShort},
-		{`a..a`, errLabelTooShort},
-		{`.a..a`, errLabelTooShort},
-		{`..foo.com`, errLabelTooShort},
-		{`.`, errNameEndsInDot},
-		{`..`, errNameEndsInDot},
-		{`a..`, errNameEndsInDot},
-		{`.....`, errNameEndsInDot},
-		{`.a.`, errNameEndsInDot},
-		{`www.zombo.com.`, errNameEndsInDot},
-		{`www.zombo_com.com`, errInvalidDNSCharacter},
-		{`\uFEFF`, errInvalidDNSCharacter}, // Byte order mark
-		{`\uFEFFwww.zombo.com`, errInvalidDNSCharacter},
-		{`www.zom\u202Ebo.com`, errInvalidDNSCharacter}, // Right-to-Left Override
-		{`\u202Ewww.zombo.com`, errInvalidDNSCharacter},
-		{`www.zom\u200Fbo.com`, errInvalidDNSCharacter}, // Right-to-Left Mark
-		{`\u200Fwww.zombo.com`, errInvalidDNSCharacter},
+		// Invalid identifier types
+		{identifier.ACMEIdentifier{}, errUnsupportedIdent}, // Empty identifier type
+		{identifier.ACMEIdentifier{Type: "fnord", Value: "uh-oh, Spaghetti-Os[tm]"}, errUnsupportedIdent},
+
+		// Empty identifier values
+		{identifier.NewDNS(``), errEmptyIdentifier},                 // Empty DNS identifier
+		{identifier.ACMEIdentifier{Type: "ip"}, errEmptyIdentifier}, // Empty IP identifier
+
+		// DNS follies
+
+		{identifier.NewDNS(`zomb!.com`), errInvalidDNSCharacter}, // ASCII character out of range
+		{identifier.NewDNS(`emailaddress@myseriously.present.com`), errInvalidDNSCharacter},
+		{identifier.NewDNS(`user:pass@myseriously.present.com`), errInvalidDNSCharacter},
+		{identifier.NewDNS(`zömbo.com`), errInvalidDNSCharacter},                              // non-ASCII character
+		{identifier.NewDNS(`127.0.0.1`), errIPAddressInDNS},                                   // IPv4 address
+		{identifier.NewDNS(`fe80::1:1`), errInvalidDNSCharacter},                              // IPv6 address
+		{identifier.NewDNS(`[2001:db8:85a3:8d3:1319:8a2e:370:7348]`), errInvalidDNSCharacter}, // unexpected IPv6 variants
+		{identifier.NewDNS(`[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443`), errInvalidDNSCharacter},
+		{identifier.NewDNS(`2001:db8::/32`), errInvalidDNSCharacter},
+		{identifier.NewDNS(`a.b.c.d.e.f.g.h.i.j.k`), errTooManyLabels}, // Too many labels (>10)
+
+		{identifier.NewDNS(`www.0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345.com`), errNameTooLong}, // Too long (254 characters)
+
+		{identifier.NewDNS(`www.ef0123456789abcdef013456789abcdef012345.789abcdef012345679abcdef0123456789abcdef01234.6789abcdef0123456789abcdef0.23456789abcdef0123456789a.cdef0123456789abcdef0123456789ab.def0123456789abcdef0123456789.bcdef0123456789abcdef012345.com`), nil}, // OK, not too long (240 characters)
+
+		{identifier.NewDNS(`www.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz.com`), errLabelTooLong}, // Label too long (>63 characters)
+
+		{identifier.NewDNS(`www.-ombo.com`), errInvalidDNSCharacter}, // Label starts with '-'
+		{identifier.NewDNS(`www.zomb-.com`), errInvalidDNSCharacter}, // Label ends with '-'
+		{identifier.NewDNS(`xn--.net`), errInvalidDNSCharacter},      // Label ends with '-'
+		{identifier.NewDNS(`-0b.net`), errInvalidDNSCharacter},       // First label begins with '-'
+		{identifier.NewDNS(`-0.net`), errInvalidDNSCharacter},        // First label begins with '-'
+		{identifier.NewDNS(`-.net`), errInvalidDNSCharacter},         // First label is only '-'
+		{identifier.NewDNS(`---.net`), errInvalidDNSCharacter},       // First label is only hyphens
+		{identifier.NewDNS(`0`), errTooFewLabels},
+		{identifier.NewDNS(`1`), errTooFewLabels},
+		{identifier.NewDNS(`*`), errMalformedWildcard},
+		{identifier.NewDNS(`**`), errTooManyWildcards},
+		{identifier.NewDNS(`*.*`), errTooManyWildcards},
+		{identifier.NewDNS(`zombo*com`), errMalformedWildcard},
+		{identifier.NewDNS(`*.com`), errICANNTLDWildcard},
+		{identifier.NewDNS(`..a`), errLabelTooShort},
+		{identifier.NewDNS(`a..a`), errLabelTooShort},
+		{identifier.NewDNS(`.a..a`), errLabelTooShort},
+		{identifier.NewDNS(`..foo.com`), errLabelTooShort},
+		{identifier.NewDNS(`.`), errNameEndsInDot},
+		{identifier.NewDNS(`..`), errNameEndsInDot},
+		{identifier.NewDNS(`a..`), errNameEndsInDot},
+		{identifier.NewDNS(`.....`), errNameEndsInDot},
+		{identifier.NewDNS(`.a.`), errNameEndsInDot},
+		{identifier.NewDNS(`www.zombo.com.`), errNameEndsInDot},
+		{identifier.NewDNS(`www.zombo_com.com`), errInvalidDNSCharacter},
+		{identifier.NewDNS(`\uFEFF`), errInvalidDNSCharacter}, // Byte order mark
+		{identifier.NewDNS(`\uFEFFwww.zombo.com`), errInvalidDNSCharacter},
+		{identifier.NewDNS(`www.zom\u202Ebo.com`), errInvalidDNSCharacter}, // Right-to-Left Override
+		{identifier.NewDNS(`\u202Ewww.zombo.com`), errInvalidDNSCharacter},
+		{identifier.NewDNS(`www.zom\u200Fbo.com`), errInvalidDNSCharacter}, // Right-to-Left Mark
+		{identifier.NewDNS(`\u200Fwww.zombo.com`), errInvalidDNSCharacter},
 		// Underscores are technically disallowed in DNS. Some DNS
 		// implementations accept them but we will be conservative.
-		{`www.zom_bo.com`, errInvalidDNSCharacter},
-		{`zombocom`, errTooFewLabels},
-		{`localhost`, errTooFewLabels},
-		{`mail`, errTooFewLabels},
+		{identifier.NewDNS(`www.zom_bo.com`), errInvalidDNSCharacter},
+		{identifier.NewDNS(`zombocom`), errTooFewLabels},
+		{identifier.NewDNS(`localhost`), errTooFewLabels},
+		{identifier.NewDNS(`mail`), errTooFewLabels},
 
 		// disallow capitalized letters for #927
-		{`CapitalizedLetters.com`, errInvalidDNSCharacter},
+		{identifier.NewDNS(`CapitalizedLetters.com`), errInvalidDNSCharacter},
 
-		{`example.acting`, errNonPublic},
-		{`example.internal`, errNonPublic},
+		{identifier.NewDNS(`example.acting`), errNonPublic},
+		{identifier.NewDNS(`example.internal`), errNonPublic},
 		// All-numeric final label not okay.
-		{`www.zombo.163`, errNonPublic},
-		{`xn--109-3veba6djs1bfxlfmx6c9g.xn--f1awi.xn--p1ai`, errMalformedIDN}, // Not in Unicode NFC
-		{`bq--abwhky3f6fxq.jakacomo.com`, errInvalidRLDH},
+		{identifier.NewDNS(`www.zombo.163`), errNonPublic},
+		{identifier.NewDNS(`xn--109-3veba6djs1bfxlfmx6c9g.xn--f1awi.xn--p1ai`), errMalformedIDN}, // Not in Unicode NFC
+		{identifier.NewDNS(`bq--abwhky3f6fxq.jakacomo.com`), errInvalidRLDH},
 		// Three hyphens starting at third second char of first label.
-		{`bq---abwhky3f6fxq.jakacomo.com`, errInvalidRLDH},
+		{identifier.NewDNS(`bq---abwhky3f6fxq.jakacomo.com`), errInvalidRLDH},
 		// Three hyphens starting at second char of first label.
-		{`h---test.hk2yz.org`, errInvalidRLDH},
-		{`co.uk`, errICANNTLD},
-		{`foo.bd`, errICANNTLD},
+		{identifier.NewDNS(`h---test.hk2yz.org`), errInvalidRLDH},
+		{identifier.NewDNS(`co.uk`), errICANNTLD},
+		{identifier.NewDNS(`foo.bd`), errICANNTLD},
+
+		// IP oopsies
+
+		{identifier.ACMEIdentifier{Type: "ip", Value: `zombo.com`}, errIPInvalid}, // That's DNS!
+
+		// Unexpected IPv4 variants
+		{identifier.ACMEIdentifier{Type: "ip", Value: `192.168.1.1.1`}, errIPInvalid},            // extra octet
+		{identifier.ACMEIdentifier{Type: "ip", Value: `192.168.1.256`}, errIPInvalid},            // octet out of range
+		{identifier.ACMEIdentifier{Type: "ip", Value: `192.168.1.a1`}, errIPInvalid},             // character out of range
+		{identifier.ACMEIdentifier{Type: "ip", Value: `192.168.1.0/24`}, errIPInvalid},           // with CIDR
+		{identifier.ACMEIdentifier{Type: "ip", Value: `192.168.1.1:443`}, errIPInvalid},          // with port
+		{identifier.ACMEIdentifier{Type: "ip", Value: `0xc0a80101`}, errIPInvalid},               // as hex
+		{identifier.ACMEIdentifier{Type: "ip", Value: `1.1.168.192.in-addr.arpa`}, errIPInvalid}, // reverse DNS
+
+		// Unexpected IPv6 variants
+		{identifier.ACMEIdentifier{Type: "ip", Value: `3fff:aaa:a:c0ff:ee:a:bad:deed:ffff`}, errIPInvalid},                                       // extra octet
+		{identifier.ACMEIdentifier{Type: "ip", Value: `3fff:aaa:a:c0ff:ee:a:bad:mead`}, errIPInvalid},                                            // character out of range
+		{identifier.ACMEIdentifier{Type: "ip", Value: `2001:db8::/32`}, errIPInvalid},                                                            // with CIDR
+		{identifier.ACMEIdentifier{Type: "ip", Value: `[3fff:aaa:a:c0ff:ee:a:bad:deed]`}, errIPInvalid},                                          // in brackets
+		{identifier.ACMEIdentifier{Type: "ip", Value: `[3fff:aaa:a:c0ff:ee:a:bad:deed]:443`}, errIPInvalid},                                      // in brackets, with port
+		{identifier.ACMEIdentifier{Type: "ip", Value: `0x3fff0aaa000ac0ff00ee000a0baddeed`}, errIPInvalid},                                       // as hex
+		{identifier.ACMEIdentifier{Type: "ip", Value: `d.e.e.d.d.a.b.0.a.0.0.0.e.e.0.0.f.f.0.c.a.0.0.0.a.a.a.0.f.f.f.3.ip6.arpa`}, errIPInvalid}, // reverse DNS
+		{identifier.ACMEIdentifier{Type: "ip", Value: `3fff:0aaa:a:c0ff:ee:a:bad:deed`}, errIPInvalid},                                           // leading 0 in 2nd octet (RFC 5952, Sec. 4.1)
+		{identifier.ACMEIdentifier{Type: "ip", Value: `3fff:aaa:0:0:0:a:bad:deed`}, errIPInvalid},                                                // lone 0s in 3rd-5th octets, :: not used (RFC 5952, Sec. 4.2.1)
+		{identifier.ACMEIdentifier{Type: "ip", Value: `3fff:aaa::c0ff:ee:a:bad:deed`}, errIPInvalid},                                             // :: used for just one empty octet (RFC 5952, Sec. 4.2.2)
+		{identifier.ACMEIdentifier{Type: "ip", Value: `3fff:aaa::ee:0:0:0`}, errIPInvalid},                                                       // :: used for the shorter of two possible collapses (RFC 5952, Sec. 4.2.3)
+		{identifier.ACMEIdentifier{Type: "ip", Value: `fe80:0:0:0:a::`}, errIPInvalid},                                                           // :: used for the last of two possible equal-length collapses (RFC 5952, Sec. 4.2.3)
+		{identifier.ACMEIdentifier{Type: "ip", Value: `3fff:aaa:a:C0FF:EE:a:bad:deed`}, errIPInvalid},                                            // alpha characters capitalized (RFC 5952, Sec. 4.3)
+		{identifier.ACMEIdentifier{Type: "ip", Value: `::ffff:192.168.1.1`}, errIPInvalid},                                                       // IPv6-encapsulated IPv4
+
+		// IANA special-purpose address blocks
+		{identifier.NewIP(netip.MustParseAddr("192.0.2.129")), errIPSpecialPurpose},                        // Documentation (TEST-NET-1)
+		{identifier.NewIP(netip.MustParseAddr("2001:db8:eee:eeee:eeee:eeee:d01:f1")), errIPSpecialPurpose}, // Documentation
 	}
 
 	// Test syntax errors
 	for _, tc := range testCases {
-		err := WellFormedIdentifiers(identifier.ACMEIdentifiers{identifier.NewDNS(tc.domain)})
+		err := WellFormedIdentifiers(identifier.ACMEIdentifiers{tc.ident})
 		if tc.err == nil {
-			test.AssertNil(t, err, fmt.Sprintf("Unexpected error for domain %q, got %s", tc.domain, err))
+			test.AssertNil(t, err, fmt.Sprintf("Unexpected error for %q identifier %q, got %s", tc.ident.Type, tc.ident.Value, err))
 		} else {
-			test.AssertError(t, err, fmt.Sprintf("Expected error for domain %q, but got none", tc.domain))
+			test.AssertError(t, err, fmt.Sprintf("Expected error for %q identifier %q, but got none", tc.ident.Type, tc.ident.Value))
 			var berr *berrors.BoulderError
 			test.AssertErrorWraps(t, err, &berr)
 			test.AssertContains(t, berr.Error(), tc.err.Error())
 		}
 	}
-
-	err := WellFormedIdentifiers(identifier.ACMEIdentifiers{identifier.NewIP(netip.MustParseAddr("9.9.9.9"))})
-	test.AssertError(t, err, "Expected error for IP, but got none")
-	var berr *berrors.BoulderError
-	test.AssertErrorWraps(t, err, &berr)
-	test.AssertContains(t, berr.Error(), errUnsupportedIdent.Error())
 }
 
 func TestWillingToIssue(t *testing.T) {
-	shouldBeBlocked := []string{
-		`highvalue.website1.org`,
-		`website2.co.uk`,
-		`www.website3.com`,
-		`lots.of.labels.website4.com`,
-		`banned.in.dc.com`,
-		`bad.brains.banned.in.dc.com`,
+	shouldBeBlocked := identifier.ACMEIdentifiers{
+		identifier.NewDNS(`highvalue.website1.org`),
+		identifier.NewDNS(`website2.co.uk`),
+		identifier.NewDNS(`www.website3.com`),
+		identifier.NewDNS(`lots.of.labels.website4.com`),
+		identifier.NewDNS(`banned.in.dc.com`),
+		identifier.NewDNS(`bad.brains.banned.in.dc.com`),
 	}
 	blocklistContents := []string{
 		`website2.com`,
@@ -153,15 +195,17 @@ func TestWillingToIssue(t *testing.T) {
 		`banned.in.dc.com`,
 	}
 
-	shouldBeAccepted := []string{
-		`lowvalue.website1.org`,
-		`website4.sucks`,
-		"www.unrelated.com",
-		"unrelated.com",
-		"www.8675309.com",
-		"8675309.com",
-		"web5ite2.com",
-		"www.web-site2.com",
+	shouldBeAccepted := identifier.ACMEIdentifiers{
+		identifier.NewDNS(`lowvalue.website1.org`),
+		identifier.NewDNS(`website4.sucks`),
+		identifier.NewDNS(`www.unrelated.com`),
+		identifier.NewDNS(`unrelated.com`),
+		identifier.NewDNS(`www.8675309.com`),
+		identifier.NewDNS(`8675309.com`),
+		identifier.NewDNS(`web5ite2.com`),
+		identifier.NewDNS(`www.web-site2.com`),
+		identifier.NewIP(netip.MustParseAddr(`9.9.9.9`)),
+		identifier.NewIP(netip.MustParseAddr(`2620:fe::fe`)),
 	}
 
 	policy := blockedNamesPolicy{
@@ -186,8 +230,8 @@ func TestWillingToIssue(t *testing.T) {
 	err = pa.WillingToIssue(identifier.ACMEIdentifiers{identifier.NewDNS("www.xn--m.com")})
 	test.AssertError(t, err, "WillingToIssue didn't fail on a malformed IDN")
 	// Invalid identifier type
-	err = pa.WillingToIssue(identifier.ACMEIdentifiers{identifier.NewIP(netip.MustParseAddr("1.1.1.1"))})
-	test.AssertError(t, err, "WillingToIssue didn't fail on an IP address")
+	err = pa.WillingToIssue(identifier.ACMEIdentifiers{identifier.ACMEIdentifier{Type: "fnord", Value: "uh-oh, Spaghetti-Os[tm]"}})
+	test.AssertError(t, err, "WillingToIssue didn't fail on an invalid identifier type")
 	// Valid encoding
 	err = pa.WillingToIssue(identifier.ACMEIdentifiers{identifier.NewDNS("www.xn--mnich-kva.com")})
 	test.AssertNotError(t, err, "WillingToIssue failed on a properly formed IDN")
@@ -197,18 +241,18 @@ func TestWillingToIssue(t *testing.T) {
 	features.Reset()
 
 	// Test expected blocked domains
-	for _, domain := range shouldBeBlocked {
-		err := pa.WillingToIssue(identifier.ACMEIdentifiers{identifier.NewDNS(domain)})
-		test.AssertError(t, err, "domain was not correctly forbidden")
+	for _, ident := range shouldBeBlocked {
+		err := pa.WillingToIssue(identifier.ACMEIdentifiers{ident})
+		test.AssertError(t, err, "identifier was not correctly forbidden")
 		var berr *berrors.BoulderError
 		test.AssertErrorWraps(t, err, &berr)
 		test.AssertContains(t, berr.Detail, errPolicyForbidden.Error())
 	}
 
 	// Test acceptance of good names
-	for _, domain := range shouldBeAccepted {
-		err := pa.WillingToIssue(identifier.ACMEIdentifiers{identifier.NewDNS(domain)})
-		test.AssertNotError(t, err, "domain was incorrectly forbidden")
+	for _, ident := range shouldBeAccepted {
+		err := pa.WillingToIssue(identifier.ACMEIdentifiers{ident})
+		test.AssertNotError(t, err, "identifier was incorrectly forbidden")
 	}
 }
 
@@ -415,15 +459,22 @@ func TestChallengeTypesFor(t *testing.T) {
 			},
 		},
 		{
-			name:  "wildcard",
+			name:  "dns wildcard",
 			ident: identifier.NewDNS("*.example.com"),
 			wantChalls: []core.AcmeChallenge{
 				core.ChallengeTypeDNS01,
 			},
 		},
 		{
-			name:    "other",
-			ident:   identifier.NewIP(netip.MustParseAddr("1.2.3.4")),
+			name:  "ip",
+			ident: identifier.NewIP(netip.MustParseAddr("1.2.3.4")),
+			wantChalls: []core.AcmeChallenge{
+				core.ChallengeTypeHTTP01, core.ChallengeTypeTLSALPN01,
+			},
+		},
+		{
+			name:    "invalid",
+			ident:   identifier.ACMEIdentifier{Type: "fnord", Value: "uh-oh, Spaghetti-Os[tm]"},
 			wantErr: "unrecognized identifier type",
 		},
 	}
@@ -573,3 +624,109 @@ func TestCheckAuthzChallenges(t *testing.T) {
 		})
 	}
 }
+
+func TestWillingToIssue_IdentifierType(t *testing.T) {
+	t.Parallel()
+
+	testCases := []struct {
+		name    string
+		ident   identifier.ACMEIdentifier
+		enabled map[identifier.IdentifierType]bool
+		wantErr string
+	}{
+		{
+			name:    "DNS identifier, none enabled",
+			ident:   identifier.NewDNS("example.com"),
+			enabled: nil,
+			wantErr: "The ACME server has disabled this identifier type",
+		},
+		{
+			name:    "DNS identifier, DNS enabled",
+			ident:   identifier.NewDNS("example.com"),
+			enabled: map[identifier.IdentifierType]bool{identifier.TypeDNS: true},
+			wantErr: "",
+		},
+		{
+			name:    "DNS identifier, DNS & IP enabled",
+			ident:   identifier.NewDNS("example.com"),
+			enabled: map[identifier.IdentifierType]bool{identifier.TypeDNS: true, identifier.TypeIP: true},
+			wantErr: "",
+		},
+		{
+			name:    "DNS identifier, IP enabled",
+			ident:   identifier.NewDNS("example.com"),
+			enabled: map[identifier.IdentifierType]bool{identifier.TypeIP: true},
+			wantErr: "The ACME server has disabled this identifier type",
+		},
+		{
+			name:    "IP identifier, none enabled",
+			ident:   identifier.NewIP(netip.MustParseAddr("9.9.9.9")),
+			enabled: nil,
+			wantErr: "The ACME server has disabled this identifier type",
+		},
+		{
+			name:    "IP identifier, DNS enabled",
+			ident:   identifier.NewIP(netip.MustParseAddr("9.9.9.9")),
+			enabled: map[identifier.IdentifierType]bool{identifier.TypeDNS: true},
+			wantErr: "The ACME server has disabled this identifier type",
+		},
+		{
+			name:    "IP identifier, DNS & IP enabled",
+			ident:   identifier.NewIP(netip.MustParseAddr("9.9.9.9")),
+			enabled: map[identifier.IdentifierType]bool{identifier.TypeDNS: true, identifier.TypeIP: true},
+			wantErr: "",
+		},
+		{
+			name:    "IP identifier, IP enabled",
+			ident:   identifier.NewIP(netip.MustParseAddr("9.9.9.9")),
+			enabled: map[identifier.IdentifierType]bool{identifier.TypeIP: true},
+			wantErr: "",
+		},
+		{
+			name:    "invalid identifier type",
+			ident:   identifier.ACMEIdentifier{Type: "drywall", Value: "oh yeah!"},
+			enabled: map[identifier.IdentifierType]bool{"drywall": true},
+			wantErr: "Invalid identifier type",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+
+			policy := blockedNamesPolicy{
+				HighRiskBlockedNames: []string{"zombo.gov.us"},
+				ExactBlockedNames:    []string{`highvalue.website1.org`},
+				AdminBlockedNames:    []string{`banned.in.dc.com`},
+			}
+
+			yamlPolicyBytes, err := yaml.Marshal(policy)
+			test.AssertNotError(t, err, "Couldn't YAML serialize blocklist")
+			yamlPolicyFile, _ := os.CreateTemp("", "test-blocklist.*.yaml")
+			defer os.Remove(yamlPolicyFile.Name())
+			err = os.WriteFile(yamlPolicyFile.Name(), yamlPolicyBytes, 0640)
+			test.AssertNotError(t, err, "Couldn't write YAML blocklist")
+
+			pa := paImpl(t)
+
+			err = pa.LoadHostnamePolicyFile(yamlPolicyFile.Name())
+			test.AssertNotError(t, err, "Couldn't load rules")
+
+			pa.enabledIdentifiers = tc.enabled
+
+			err = pa.WillingToIssue(identifier.ACMEIdentifiers{tc.ident})
+
+			if tc.wantErr == "" {
+				if err != nil {
+					t.Errorf("should have succeeded, but got error: %s", err.Error())
+				}
+			} else {
+				if err == nil {
+					t.Errorf("should have failed")
+				} else if !strings.Contains(err.Error(), tc.wantErr) {
+					t.Errorf("wrong error; wanted '%s', but got '%s'", tc.wantErr, err.Error())
+				}
+			}
+		})
+	}
+}
diff --git a/ra/ra_test.go b/ra/ra_test.go
index 90828aa05..4c4840017 100644
--- a/ra/ra_test.go
+++ b/ra/ra_test.go
@@ -301,10 +301,16 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, sapb.StorageAutho
 	}
 	va := va.RemoteClients{VAClient: dummyVA, CAAClient: dummyVA}
 
-	pa, err := policy.New(map[core.AcmeChallenge]bool{
-		core.ChallengeTypeHTTP01: true,
-		core.ChallengeTypeDNS01:  true,
-	}, blog.NewMock())
+	pa, err := policy.New(
+		map[identifier.IdentifierType]bool{
+			identifier.TypeDNS: true,
+			identifier.TypeIP:  true,
+		},
+		map[core.AcmeChallenge]bool{
+			core.ChallengeTypeHTTP01: true,
+			core.ChallengeTypeDNS01:  true,
+		},
+		blog.NewMock())
 	test.AssertNotError(t, err, "Couldn't create PA")
 	err = pa.LoadHostnamePolicyFile("../test/hostname-policy.yaml")
 	test.AssertNotError(t, err, "Couldn't set hostname policy")
@@ -2795,10 +2801,16 @@ func TestFinalizeOrderDisabledChallenge(t *testing.T) {
 	test.AssertNotError(t, err, "Error creating policy forbid CSR")
 
 	// Replace the Policy Authority with one which has this challenge type disabled
-	pa, err := policy.New(map[core.AcmeChallenge]bool{
-		core.ChallengeTypeDNS01:     true,
-		core.ChallengeTypeTLSALPN01: true,
-	}, ra.log)
+	pa, err := policy.New(
+		map[identifier.IdentifierType]bool{
+			identifier.TypeDNS: true,
+			identifier.TypeIP:  true,
+		},
+		map[core.AcmeChallenge]bool{
+			core.ChallengeTypeDNS01:     true,
+			core.ChallengeTypeTLSALPN01: true,
+		},
+		ra.log)
 	test.AssertNotError(t, err, "creating test PA")
 	err = pa.LoadHostnamePolicyFile("../test/hostname-policy.yaml")
 	test.AssertNotError(t, err, "loading test hostname policy")
@@ -3199,7 +3211,7 @@ func TestUpdateMissingAuthorization(t *testing.T) {
 func TestPerformValidationBadChallengeType(t *testing.T) {
 	_, _, ra, _, fc, cleanUp := initAuthorities(t)
 	defer cleanUp()
-	pa, err := policy.New(map[core.AcmeChallenge]bool{}, blog.NewMock())
+	pa, err := policy.New(map[identifier.IdentifierType]bool{}, map[core.AcmeChallenge]bool{}, blog.NewMock())
 	test.AssertNotError(t, err, "Couldn't create PA")
 	ra.PA = pa
 
@@ -4091,10 +4103,16 @@ func TestGetAuthorization(t *testing.T) {
 	}
 
 	// With HTTP01 enabled, GetAuthorization should pass the mock challenge through.
-	pa, err := policy.New(map[core.AcmeChallenge]bool{
-		core.ChallengeTypeHTTP01: true,
-		core.ChallengeTypeDNS01:  true,
-	}, blog.NewMock())
+	pa, err := policy.New(
+		map[identifier.IdentifierType]bool{
+			identifier.TypeDNS: true,
+			identifier.TypeIP:  true,
+		},
+		map[core.AcmeChallenge]bool{
+			core.ChallengeTypeHTTP01: true,
+			core.ChallengeTypeDNS01:  true,
+		},
+		blog.NewMock())
 	test.AssertNotError(t, err, "Couldn't create PA")
 	ra.PA = pa
 	authz, err := ra.GetAuthorization(context.Background(), &rapb.GetAuthorizationRequest{Id: 1})
@@ -4103,9 +4121,15 @@ func TestGetAuthorization(t *testing.T) {
 	test.AssertEquals(t, authz.Challenges[0].Type, string(core.ChallengeTypeHTTP01))
 
 	// With HTTP01 disabled, GetAuthorization should filter out the mock challenge.
-	pa, err = policy.New(map[core.AcmeChallenge]bool{
-		core.ChallengeTypeDNS01: true,
-	}, blog.NewMock())
+	pa, err = policy.New(
+		map[identifier.IdentifierType]bool{
+			identifier.TypeDNS: true,
+			identifier.TypeIP:  true,
+		},
+		map[core.AcmeChallenge]bool{
+			core.ChallengeTypeDNS01: true,
+		},
+		blog.NewMock())
 	test.AssertNotError(t, err, "Couldn't create PA")
 	ra.PA = pa
 	authz, err = ra.GetAuthorization(context.Background(), &rapb.GetAuthorizationRequest{Id: 1})
diff --git a/ratelimits/names_test.go b/ratelimits/names_test.go
index 89eca6baa..0dff15415 100644
--- a/ratelimits/names_test.go
+++ b/ratelimits/names_test.go
@@ -205,7 +205,7 @@ func TestValidateIdForName(t *testing.T) {
 			limit: CertificatesPerDomain,
 			desc:  "empty domain",
 			id:    "",
-			err:   "name is empty",
+			err:   "Identifier value (name) is empty",
 		},
 		{
 			limit: CertificatesPerFQDNSet,
diff --git a/test/config-next/ca.json b/test/config-next/ca.json
index 742f686db..756d17ad5 100644
--- a/test/config-next/ca.json
+++ b/test/config-next/ca.json
@@ -190,6 +190,10 @@
 			"http-01": true,
 			"dns-01": true,
 			"tls-alpn-01": true
+		},
+		"identifiers": {
+			"dns": true,
+			"ip": true
 		}
 	},
 	"syslog": {
diff --git a/test/config-next/cert-checker.json b/test/config-next/cert-checker.json
index 723912baf..47ab4d6de 100644
--- a/test/config-next/cert-checker.json
+++ b/test/config-next/cert-checker.json
@@ -29,6 +29,10 @@
 			"http-01": true,
 			"dns-01": true,
 			"tls-alpn-01": true
+		},
+		"identifiers": {
+			"dns": true,
+			"ip": true
 		}
 	},
 	"syslog": {
diff --git a/test/config-next/ra.json b/test/config-next/ra.json
index faa5c1170..cd5423f13 100644
--- a/test/config-next/ra.json
+++ b/test/config-next/ra.json
@@ -181,6 +181,10 @@
 			"http-01": true,
 			"dns-01": true,
 			"tls-alpn-01": true
+		},
+		"identifiers": {
+			"dns": true,
+			"ip": true
 		}
 	},
 	"syslog": {
diff --git a/test/config-next/va.json b/test/config-next/va.json
index 4f667d782..f9ea246f5 100644
--- a/test/config-next/va.json
+++ b/test/config-next/va.json
@@ -38,8 +38,7 @@
 			}
 		},
 		"features": {
-			"DOH": true,
-			"MPICFullResults": true
+			"DOH": true
 		},
 		"remoteVAs": [
 			{
diff --git a/test/integration/validation_test.go b/test/integration/validation_test.go
index 3738e5bf1..7e0d28e88 100644
--- a/test/integration/validation_test.go
+++ b/test/integration/validation_test.go
@@ -32,46 +32,17 @@ func collectUserAgentsFromDNSRequests(requests []challtestsrvclient.DNSRequest)
 func assertUserAgentsLength(t *testing.T, got []string, checkType string) {
 	t.Helper()
 
-	if os.Getenv("BOULDER_CONFIG_DIR") != "test/config-next" {
-		// We only need 3 checks if the MPICFullResults feature-flag is not
-		// enabled.
-		//
-		// TODO(#8121): Remove this once MPICFullResults has been defaulted to
-		// true.
-		if len(got) != 4 && len(got) != 3 {
-			t.Errorf("During %s, expected 3 or 4 User-Agents, got %d", checkType, len(got))
-		}
-	} else {
-		if len(got) != 4 {
-			t.Errorf("During %s, expected 4 User-Agents, got %d", checkType, len(got))
-		}
+	if len(got) != 4 {
+		t.Errorf("During %s, expected 4 User-Agents, got %d", checkType, len(got))
 	}
 }
 
 func assertExpectedUserAgents(t *testing.T, got []string, checkType string) {
 	t.Helper()
 
-	if os.Getenv("BOULDER_CONFIG_DIR") != "test/config-next" {
-		// One User-Agent may be missing if the MPICFullResults feature-flag is
-		// not enabled. This will need to be modified to 2 if we have not
-		// removed this feature-flag by the time we get to 6+ perspectives.
-		//
-		// TODO(#8121): Remove this once MPICFullResults has been defaulted to
-		// true.
-		var alreadySkippedOne bool
-		for _, ua := range expectedUserAgents {
-			if !slices.Contains(got, ua) {
-				if alreadySkippedOne {
-					t.Errorf("During %s, missing more than 1 User-Agent in %s (got %v)", checkType, expectedUserAgents, got)
-				}
-				alreadySkippedOne = true
-			}
-		}
-	} else {
-		for _, ua := range expectedUserAgents {
-			if !slices.Contains(got, ua) {
-				t.Errorf("During %s, expected User-Agent %q in %s (got %v)", checkType, ua, expectedUserAgents, got)
-			}
+	for _, ua := range expectedUserAgents {
+		if !slices.Contains(got, ua) {
+			t.Errorf("During %s, expected User-Agent %q in %s (got %v)", checkType, ua, expectedUserAgents, got)
 		}
 	}
 }
@@ -129,15 +100,8 @@ func TestMPICTLSALPN01(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	if os.Getenv("BOULDER_CONFIG_DIR") == "test/config-next" && len(validationEvents) != 4 {
+	if len(validationEvents) != 4 {
 		t.Errorf("expected 4 validation events got %d", len(validationEvents))
-	} else if len(validationEvents) != 3 && len(validationEvents) != 4 {
-		// We only need 3 checks if the MPICFullResults feature-flag is not
-		// enabled.
-		//
-		// TODO(#8121): Remove this once MPICFullResults has been defaulted to
-		// true.
-		t.Errorf("expected 3 or 4 validation events got %d", len(validationEvents))
 	}
 
 	dnsEvents, err := testSrvClient.DNSRequestHistory(domain)
diff --git a/test/ocsp/checkocsp/checkocsp.go b/test/ocsp/checkocsp/checkocsp.go
index 52a52f9b4..4a42659ff 100644
--- a/test/ocsp/checkocsp/checkocsp.go
+++ b/test/ocsp/checkocsp/checkocsp.go
@@ -48,7 +48,6 @@ stale.
 			}
 			serialNumber := big.NewInt(0).SetBytes(bytes)
 			_, err = helper.ReqSerial(serialNumber, config)
-
 		} else {
 			_, err = helper.ReqFile(a, config)
 		}
diff --git a/va/va_test.go b/va/va_test.go
index df93daf00..448261382 100644
--- a/va/va_test.go
+++ b/va/va_test.go
@@ -12,7 +12,6 @@ import (
 	"net/http/httptest"
 	"net/netip"
 	"os"
-	"strconv"
 	"strings"
 	"sync"
 	"syscall"
@@ -237,11 +236,6 @@ type multiSrv struct {
 	allowedUAs map[string]bool
 }
 
-const (
-	slowUA                = "slow"
-	slowRemoteSleepMillis = 100
-)
-
 func httpMultiSrv(t *testing.T, token string, allowedUAs map[string]bool) *multiSrv {
 	t.Helper()
 	m := http.NewServeMux()
@@ -250,9 +244,6 @@ func httpMultiSrv(t *testing.T, token string, allowedUAs map[string]bool) *multi
 	ms := &multiSrv{server, sync.Mutex{}, allowedUAs}
 
 	m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
-		if r.UserAgent() == slowUA {
-			time.Sleep(slowRemoteSleepMillis * time.Millisecond)
-		}
 		ms.mu.Lock()
 		defer ms.mu.Unlock()
 		if ms.allowedUAs[r.UserAgent()] {
@@ -645,7 +636,7 @@ func TestMultiVA(t *testing.T) {
 		},
 		{
 			// With the local and remote VAs seeing diff problems, we expect a problem.
-			Name: "Local and remote VA differential, full results, enforce multi VA",
+			Name: "Local and remote VA differential",
 			Remotes: []remoteConf{
 				{ua: fail, rir: arin},
 				{ua: fail, rir: ripe},
@@ -689,143 +680,6 @@ func TestMultiVA(t *testing.T) {
 	}
 }
 
-func TestMultiVAEarlyReturn(t *testing.T) {
-	// TODO(#7809): Make this test parallel when it no longer manipulates the
-	// MPICFullResults feature flag.
-
-	testCases := []struct {
-		name              string
-		remoteConfs       []remoteConf
-		wantCorroboration bool
-		wantEarlyReturn   bool
-	}{
-		{
-			name: "Early return when 2/3 pass",
-			remoteConfs: []remoteConf{
-				{ua: pass, rir: arin},
-				{ua: pass, rir: ripe},
-				{ua: slowUA, rir: apnic},
-			},
-			wantCorroboration: true,
-			wantEarlyReturn:   true,
-		},
-		{
-			name: "Early return when 2/3 fail",
-			remoteConfs: []remoteConf{
-				{ua: fail, rir: arin},
-				{ua: fail, rir: ripe},
-				{ua: slowUA, rir: apnic},
-			},
-			wantCorroboration: false,
-			wantEarlyReturn:   true,
-		},
-		{
-			name: "Slow return when first 2/3 are inconclusive",
-			remoteConfs: []remoteConf{
-				{ua: pass, rir: arin},
-				{ua: fail, rir: ripe},
-				{ua: slowUA, rir: apnic},
-			},
-			wantCorroboration: false,
-			wantEarlyReturn:   false,
-		},
-		{
-			name: "Early return when 4/6 pass",
-			remoteConfs: []remoteConf{
-				{ua: pass, rir: arin},
-				{ua: pass, rir: ripe},
-				{ua: pass, rir: apnic},
-				{ua: pass, rir: arin},
-				{ua: fail, rir: ripe},
-				{ua: slowUA, rir: apnic},
-			},
-			wantCorroboration: true,
-			wantEarlyReturn:   true,
-		},
-		{
-			name: "Early return when 4/6 fail",
-			remoteConfs: []remoteConf{
-				{ua: pass, rir: arin},
-				{ua: fail, rir: ripe},
-				{ua: fail, rir: apnic},
-				{ua: fail, rir: arin},
-				{ua: fail, rir: ripe},
-				{ua: slowUA, rir: apnic},
-			},
-			wantCorroboration: false,
-			wantEarlyReturn:   true,
-		},
-		{
-			name: "Slow return when first 5/6 are inconclusive",
-			remoteConfs: []remoteConf{
-				{ua: pass, rir: arin},
-				{ua: pass, rir: ripe},
-				{ua: pass, rir: apnic},
-				{ua: fail, rir: arin},
-				{ua: fail, rir: ripe},
-				{ua: slowUA, rir: apnic},
-			},
-			wantCorroboration: false,
-			wantEarlyReturn:   false,
-		},
-	}
-
-	for _, mpicFullResults := range []bool{false, true} {
-		for _, tc := range testCases {
-			t.Run(fmt.Sprintf("%s_(MPICFullResults: %s)", tc.name, strconv.FormatBool(mpicFullResults)), func(t *testing.T) {
-				// TODO(#7809): Make this test parallel when it no longer manipulates the
-				// MPICFullResults feature flag.
-
-				// Configure one test server per test case so that all tests can run in parallel.
-				ms := httpMultiSrv(t, expectedToken, map[string]bool{pass: true, fail: false})
-				defer ms.Close()
-
-				localVA, _ := setupWithRemotes(ms.Server, pass, tc.remoteConfs, nil)
-
-				features.Set(features.Config{MPICFullResults: mpicFullResults})
-				defer features.Reset()
-
-				// Perform all validations
-				start := time.Now()
-				req := createValidationRequest(identifier.NewDNS("localhost"), core.ChallengeTypeHTTP01)
-				res, _ := localVA.DoDCV(ctx, req)
-
-				if tc.wantCorroboration {
-					if res.Problem != nil {
-						t.Errorf("expected corroboration, but got prob %s", res.Problem)
-					}
-				} else {
-					if res.Problem == nil {
-						t.Error("expected prob from PerformValidation, got nil")
-					}
-				}
-
-				elapsed := time.Since(start).Round(time.Millisecond).Milliseconds()
-
-				if tc.wantEarlyReturn && !mpicFullResults {
-					// The slow UA should sleep for `slowRemoteSleepMillis`. But the first remote
-					// VA should fail quickly and the early-return code should cause the overall
-					// overall validation to return a prob quickly (i.e. in less than half of
-					// `slowRemoteSleepMillis`).
-					if elapsed > slowRemoteSleepMillis/2 {
-						t.Errorf(
-							"Expected an early return from PerformValidation in < %d ms, took %d ms",
-							slowRemoteSleepMillis/2, elapsed)
-					}
-				} else {
-					// The VA will have to wait for all of the results, because the fast
-					// results aren't sufficient to determine (non)corroboration.
-					if elapsed < slowRemoteSleepMillis {
-						t.Errorf(
-							"Expected a slow return from PerformValidation in >= %d ms, took %d ms",
-							slowRemoteSleepMillis, elapsed)
-					}
-				}
-			})
-		}
-	}
-}
-
 func TestMultiVAPolicy(t *testing.T) {
 	t.Parallel()